View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.util;
23  
24  import static com.entwinemedia.fn.Stream.$;
25  import static org.opencastproject.util.EqualsUtil.eq;
26  import static org.opencastproject.util.data.Option.option;
27  
28  import org.opencastproject.util.data.Function0;
29  
30  import com.entwinemedia.fn.Fn;
31  import com.entwinemedia.fn.Fn2;
32  import com.entwinemedia.fn.Stream;
33  import com.entwinemedia.fn.data.Opt;
34  import com.entwinemedia.fn.fns.Booleans;
35  
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Map.Entry;
42  
43  import javax.xml.XMLConstants;
44  import javax.xml.namespace.NamespaceContext;
45  
46  public final class XmlNamespaceContext implements NamespaceContext {
47    // the number of default bindings
48    private static final int DEFAULT_BINDINGS = 2;
49  
50    // prefix -> namespace URI
51    private final Map<String, String> prefixToUri = new HashMap<String, String>();
52  
53    /**
54     * Create a new namespace context with bindings from prefix to URI and bind the
55     * default namespaces as described in the documentation of {@link javax.xml.namespace.NamespaceContext}.
56     */
57    public XmlNamespaceContext(Map<String, String> prefixToUri) {
58      this.prefixToUri.putAll(prefixToUri);
59      this.prefixToUri.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
60      this.prefixToUri.put(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
61    }
62  
63    public static XmlNamespaceContext mk(Map<String, String> prefixToUri) {
64      return new XmlNamespaceContext(prefixToUri);
65    }
66  
67    public static XmlNamespaceContext mk(XmlNamespaceBinding... bindings) {
68      return mk($(bindings));
69    }
70  
71    public static XmlNamespaceContext mk(String prefix, String namespaceUri) {
72      return new XmlNamespaceContext(Collections.singletonMap(prefix, namespaceUri));
73    }
74  
75    public static XmlNamespaceContext mk(List<XmlNamespaceBinding> bindings) {
76      return mk($(bindings));
77    }
78  
79    public static XmlNamespaceContext mk(Stream<XmlNamespaceBinding> bindings) {
80      return new XmlNamespaceContext(
81              bindings.foldl(
82                      new HashMap<String, String>(),
83                      new Fn2<HashMap<String, String>, XmlNamespaceBinding, HashMap<String, String>>() {
84                        @Override
85                        public HashMap<String, String> apply(
86                                HashMap<String, String> prefixToUri, XmlNamespaceBinding binding) {
87                          prefixToUri.put(binding.getPrefix(), binding.getNamespaceURI());
88                          return prefixToUri;
89                        }
90                      }));
91    }
92  
93    @Override
94    public String getNamespaceURI(String prefix) {
95      return Opt.nul(prefixToUri.get(prefix)).getOr(XMLConstants.NULL_NS_URI);
96    }
97  
98    @Override
99    public String getPrefix(String uri) {
100     return $(prefixToUri.entrySet()).find(Booleans.eq(RequireUtil.notNull(uri, "uri")).o(value)).map(key).orNull();
101   }
102 
103   @Override
104   public Iterator getPrefixes(String uri) {
105     return $(prefixToUri.entrySet()).filter(Booleans.eq(uri).o(value)).map(key).iterator();
106   }
107 
108   public List<XmlNamespaceBinding> getBindings() {
109     return $(prefixToUri.entrySet()).map(toBinding).toList();
110   }
111 
112   /** Create a new context with the given bindings added. Existing bindings will not be overwritten. */
113   public XmlNamespaceContext add(XmlNamespaceBinding... bindings) {
114     return add($(bindings));
115   }
116 
117   /** Create a new context with the given bindings added. Existing bindings will not be overwritten. */
118   public XmlNamespaceContext add(XmlNamespaceContext bindings) {
119     if (bindings.prefixToUri.size() == DEFAULT_BINDINGS) {
120       // bindings contains only the default bindings
121       return this;
122     } else {
123       return add($(bindings.getBindings()));
124     }
125   }
126 
127   private XmlNamespaceContext add(Stream<XmlNamespaceBinding> bindings) {
128     return mk(bindings.append(getBindings()));
129   }
130 
131   private static final Fn<Entry<String, String>, String> key = new Fn<Entry<String, String>, String>() {
132     @Override public String apply(Entry<String, String> e) {
133       return e.getKey();
134     }
135   };
136 
137   private static final Fn<Entry<String, String>, String> value = new Fn<Entry<String, String>, String>() {
138     @Override public String apply(Entry<String, String> e) {
139       return e.getValue();
140     }
141   };
142 
143   private static final Fn<Entry<String, String>, XmlNamespaceBinding> toBinding = new Fn<Entry<String, String>, XmlNamespaceBinding>() {
144     @Override public XmlNamespaceBinding apply(Entry<String, String> e) {
145       return new XmlNamespaceBinding(e.getKey(), e.getValue());
146     }
147   };
148 
149   public NamespaceContext merge(final NamespaceContext precedence) {
150     return merge(this, precedence);
151   }
152 
153   /** Merge <code>b</code> into <code>a</code> so that <code>b</code> takes precedence over <code>a</code>. */
154   public static NamespaceContext merge(final NamespaceContext a, final NamespaceContext b) {
155     return new NamespaceContext() {
156       @Override public String getNamespaceURI(String prefix) {
157         final String uri = b.getNamespaceURI(prefix);
158         if (eq(XMLConstants.DEFAULT_NS_PREFIX, prefix) && eq(XMLConstants.NULL_NS_URI, uri)) {
159           return a.getNamespaceURI(prefix);
160         } else {
161           return uri;
162         }
163       }
164 
165       @Override public String getPrefix(final String uri) {
166         return option(b.getPrefix(uri)).getOrElse(new Function0<String>() {
167           @Override public String apply() {
168             return a.getPrefix(uri);
169           }
170         });
171       }
172 
173       @Override public Iterator getPrefixes(String uri) {
174         final Iterator prefixes = b.getPrefixes(uri);
175         if (prefixes.hasNext()) {
176           return prefixes;
177         } else {
178           return a.getPrefixes(uri);
179         }
180       }
181     };
182   }
183 }