View Javadoc

1   /*
2    * Copyright 2010 University Corporation for Advanced Internet Development, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.xml;
18  
19  import java.util.Collection;
20  
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import javax.xml.namespace.QName;
27  
28  import org.opensaml.xml.util.DatatypeHelper;
29  import org.opensaml.xml.util.LazyMap;
30  import org.opensaml.xml.util.LazySet;
31  import org.opensaml.xml.util.XMLConstants;
32  
33  /**
34   * A class which is responsible for managing XML namespace-related data for an {@link XMLObject}.
35   * 
36   * <p>
37   * Code which mutates the state of an XMLObject such that XML namespace-related data is also logically changed,
38   * should call the appropriate method, based on the type of change being made.
39   * </p>
40   */
41  public class NamespaceManager {
42      
43      /** The token used to represent the default namespace in {@link #getNonVisibleNamespacePrefixes()}. */
44      public static final String DEFAULT_NS_TOKEN = "#default";
45      
46      /** The 'xml' namespace. */
47      private static final Namespace XML_NAMESPACE = 
48          new Namespace(XMLConstants.XML_NS, XMLConstants.XML_PREFIX);
49      
50      /** The 'xsi' namespace. */
51      private static final Namespace XSI_NAMESPACE = 
52          new Namespace(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX);
53      
54      /** The owning XMLObject. */
55      private XMLObject owner;
56      
57      /** XMLObject name namespace. */
58      private Namespace elementName;
59      
60      /** XMLObject type namespace. */
61      private Namespace elementType;
62      
63      /** Explicitly declared namespaces. */
64      private Set<Namespace> decls;
65      
66      /** Indeterminate namespace usage. */
67      private Set<Namespace> usage;
68      
69      /** Registered namespaces of attribute names. */
70      private Set<Namespace> attrNames;
71      
72      /** Registered namespaces of attribute values. */
73      private Map<String, Namespace> attrValues;
74      
75      /** Registered namespaces of content values. */
76      private Namespace contentValue;
77      
78      /**
79       * Constructor.
80       *
81       * @param owningObject the XMLObject whose namespace info is to be managed
82       */
83      public NamespaceManager(XMLObject owningObject) {
84          owner = owningObject;
85          
86          decls = new LazySet<Namespace>();
87          usage = new LazySet<Namespace>();
88          attrNames = new LazySet<Namespace>();
89          attrValues = new LazyMap<String, Namespace>();
90      }
91      
92      /**
93       * From an QName representing a qualified attribute name, generate an attribute ID
94       * suitable for use in {@link #registerAttributeValue(String, QName)} 
95       * and {@link #deregisterAttributeValue(String)}.
96       * 
97       * @param name attribute name as a QName
98       * @return a string attribute ID
99       */
100     public static String generateAttributeID(QName name) {
101        return name.toString(); 
102     }
103     
104     /**
105      * Get the owning XMLObject instance.
106      * 
107      * @return the owning XMLObject
108      */
109     public XMLObject getOwner() {
110         return owner;
111     }
112     
113     /**
114      * Get the set of namespaces currently in use on the owning XMLObject.
115      * 
116      * @return the set of namespaces
117      */
118     public Set<Namespace> getNamespaces() {
119         Set<Namespace> namespaces = mergeNamespaceCollections(decls, usage, attrNames, attrValues.values());
120         addNamespace(namespaces, getElementNameNamespace());
121         addNamespace(namespaces, getElementTypeNamespace());
122         addNamespace(namespaces, contentValue);
123         return namespaces;
124     }
125     
126     
127     /**
128      * Register usage of a namespace in some indeterminate fashion.
129      * 
130      * <p>
131      * Other methods which indicate specific usage should be preferred over this one.  This
132      * method exists primarily for backward-compatibility support for {@link XMLObject#addNamespace(Namespace)}.
133      * </p>
134      * 
135      * @param namespace namespace to register
136      */
137     public void registerNamespace(Namespace namespace) {
138         addNamespace(usage, namespace);
139     }
140     
141     /**
142      * Deregister usage of a namespace in some indeterminate fashion.
143      * 
144      * <p>
145      * Other methods which indicate specific usage should be preferred over this one.  This
146      * method exists primarily for backward-compatibility support for {@link XMLObject#removeNamespace(Namespace)}.
147      * </p>
148      * 
149      * @param namespace namespace to deregister
150      */
151     public void deregisterNamespace(Namespace namespace) {
152         removeNamespace(usage, namespace);
153     }
154     
155     /**
156      * Register a namespace declaration.
157      * 
158      * @param namespace the namespace to register
159      */
160     public void registerNamespaceDeclaration(Namespace namespace) {
161         namespace.setAlwaysDeclare(true);
162         addNamespace(decls, namespace);
163     }
164     
165     /**
166      * Deregister a namespace declaration.
167      * 
168      * @param namespace the namespace to deregister
169      */
170     public void deregisterNamespaceDeclaration(Namespace namespace) {
171         removeNamespace(decls, namespace);
172     }
173     
174     /**
175      * Register a namespace-qualified attribute name.
176      * 
177      * @param attributeName the attribute name to register
178      */
179     public void registerAttributeName(QName attributeName) {
180         if (checkQName(attributeName)) {
181             addNamespace(attrNames, buildNamespace(attributeName));
182         }
183     }
184     
185     /**
186      * Deregister a namespace-qualified attribute name.
187      * 
188      * @param attributeName the attribute name to deregister
189      */
190     public void deregisterAttributeName(QName attributeName) {
191         if (checkQName(attributeName)) {
192             removeNamespace(attrNames, buildNamespace(attributeName));
193         }
194     }
195     
196     /**
197      * Register a QName attribute value.
198      * 
199      * @param attributeID unique identifier for the attribute within the XMLObject's content model
200      * @param attributeValue the QName value to register
201      */
202     public void registerAttributeValue(String attributeID, QName attributeValue) {
203         if (checkQName(attributeValue)) {
204             attrValues.put(attributeID, buildNamespace(attributeValue));
205         }
206     }
207     
208     /**
209      * Deregister a QName attribute value.
210      * 
211      * @param attributeID unique identifier for the attribute within the XMLObject's content model
212      */
213     public void deregisterAttributeValue(String attributeID) {
214         attrValues.remove(attributeID);
215     }
216     
217     /**
218      * Register a QName element content value.
219      * 
220      * @param content the QName value to register
221      */
222     public void registerContentValue(QName content) {
223         if (checkQName(content)) {
224             contentValue = buildNamespace(content);
225         }
226     }
227     
228     /**
229      * Deregister a QName content value.
230      * 
231      */
232     public void deregisterContentValue() {
233         contentValue = null;
234     }
235     
236     /**
237      * Obtain the set of namespace prefixes used in a non-visible manner on owning XMLObject
238      * and its children.
239      * 
240      * <p>
241      * The primary use case for this information is to support the inclusive prefixes
242      * information that may optionally be supplied as a part of XML exclusive canonicalization.
243      * </p>
244      * 
245      * @return the set of non-visibly used namespace prefixes
246      */
247     public Set<String> getNonVisibleNamespacePrefixes() {
248         LazySet<String> prefixes = new LazySet<String>();
249         addPrefixes(prefixes, getNonVisibleNamespaces());
250         return prefixes;
251     }
252     
253     /**
254      * Obtain the set of namespaces used in a non-visible manner on owning XMLObject
255      * and its children.
256      * 
257      * <p>
258      * The primary use case for this information is to support the inclusive prefixes
259      * information that may optionally be supplied as a part of XML exclusive canonicalization.
260      * </p>
261      * 
262      * <p>
263      * The Namespace instances themselves will be copied before being returned, so
264      * modifications to them do not affect the actual Namespace instances in the
265      * underlying tree. The original <code>alwaysDeclare</code> property is not preserved.
266      * </p>
267      * 
268      * @return the set of non-visibly used namespaces 
269      */
270     public Set<Namespace> getNonVisibleNamespaces() {
271         LazySet<Namespace> nonVisibleCandidates = new LazySet<Namespace>();
272 
273         // Collect each child's non-visible namespaces
274         List<XMLObject> children = getOwner().getOrderedChildren();
275         if (children != null) {
276             for(XMLObject child : getOwner().getOrderedChildren()) {
277                 if (child != null) {
278                     Set<Namespace> childNonVisibleNamespaces = child.getNamespaceManager().getNonVisibleNamespaces();
279                     if (childNonVisibleNamespaces != null && ! childNonVisibleNamespaces.isEmpty()) {
280                         nonVisibleCandidates.addAll(childNonVisibleNamespaces);
281                     }
282                 }
283             }
284         }
285 
286         // Collect this node's non-visible candidate namespaces
287         nonVisibleCandidates.addAll(getNonVisibleNamespaceCandidates());
288 
289         // Now subtract this object's visible namespaces
290         nonVisibleCandidates.removeAll(getVisibleNamespaces());
291         
292         // As a special case, never return the 'xml' prefix.
293         nonVisibleCandidates.remove(XML_NAMESPACE);
294 
295         // What remains is the effective set of non-visible namespaces
296         // for the subtree rooted at this node.
297         return nonVisibleCandidates;
298 
299     }
300     
301     /**
302      * Get the set of all namespaces which are in scope within the subtree rooted
303      * at the owning XMLObject.
304      * 
305      * <p>
306      * The Namespace instances themselves will be copied before being returned, so
307      * modifications to them do not affect the actual Namespace instances in the
308      * underlying tree. The original <code>alwaysDeclare</code> property is not preserved.
309      * </p>
310      * 
311      * @return set of all namespaces in scope for the owning object
312      */
313     public Set<Namespace> getAllNamespacesInSubtreeScope() {
314         LazySet<Namespace> namespaces = new LazySet<Namespace>();
315 
316         // Collect namespaces for the subtree rooted at each child
317         List<XMLObject> children = getOwner().getOrderedChildren();
318         if (children != null) {
319             for(XMLObject child : getOwner().getOrderedChildren()) {
320                 if (child != null) {
321                     Set<Namespace> childNamespaces = child.getNamespaceManager().getAllNamespacesInSubtreeScope();
322                     if (childNamespaces != null && ! childNamespaces.isEmpty()) {
323                         namespaces.addAll(childNamespaces);
324                     }
325                 }
326             }
327         }
328 
329         // Collect this node's namespaces.  Copy before adding to the set. Do not preserve alwaysDeclare.
330         for (Namespace myNS : getNamespaces()) {
331             namespaces.add(copyNamespace(myNS));
332         }
333 
334         return namespaces;
335     }
336     
337     /**
338      * Register the owning XMLObject's element name.
339      * 
340      * @param name the element name to register
341      */
342     public void registerElementName(QName name) {
343         if (checkQName(name)) {
344             elementName = buildNamespace(name);
345         }
346     }
347 
348     /**
349      * Register the owning XMLObject's element type, if explicitly declared via an xsi:type.
350      * 
351      * @param type the element type to register
352      */
353     public void registerElementType(QName type) {
354         if (type != null) {
355             if (checkQName(type)) {
356                 elementType = buildNamespace(type);
357             }
358         } else {
359             elementType = null;
360         }
361     }
362     
363     /**
364      * Return a Namespace instance representing the namespace of the element name.
365      * 
366      * @return the element name's namespace
367      */
368     private Namespace getElementNameNamespace() {
369         if (elementName == null && checkQName(owner.getElementQName())) {
370             elementName = buildNamespace(owner.getElementQName());
371         }
372         return elementName;
373     }
374 
375     /**
376      * Return a Namespace instance representing the namespace of the element type, if known.
377      * 
378      * @return the element type's namespace
379      */
380     private Namespace getElementTypeNamespace() {
381         if (elementType == null) {
382             QName type = owner.getSchemaType();
383             if (type != null && checkQName(type)) {
384                 elementType = buildNamespace(type);
385             }
386         }
387         return elementType;
388     }
389     
390     /**
391      * Build a {@link Namespace} instance from a {@link QName}.
392      * 
393      * @param name the source QName 
394      * @return a Namespace built using the information in the QName
395      */
396     private Namespace buildNamespace(QName name) {
397         String uri = DatatypeHelper.safeTrimOrNullString(name.getNamespaceURI());
398         if (uri == null) {
399             throw new IllegalArgumentException("A non-empty namespace URI must be supplied");
400         }
401         String prefix = DatatypeHelper.safeTrimOrNullString(name.getPrefix());
402         return new Namespace(uri, prefix);
403     }
404     
405     /**
406      * Add a Namespace to a set of Namespaces.  Namespaces with identical URI and prefix will be treated as equivalent.
407      * An <code>alwaysDeclare</code> property of true will take precedence over a value of false.
408      * 
409      * @param namespaces the set of namespaces
410      * @param newNamespace the namespace to add to the set
411      */
412     private void addNamespace(Set<Namespace> namespaces, Namespace newNamespace) {
413         if (newNamespace == null) {
414             return;
415         }
416         
417         if (namespaces.size() == 0) {
418             namespaces.add(newNamespace);
419             return;
420         }
421         
422         for (Namespace namespace : namespaces) {
423             if (DatatypeHelper.safeEquals(namespace.getNamespaceURI(), newNamespace.getNamespaceURI()) &&
424                     DatatypeHelper.safeEquals(namespace.getNamespacePrefix(), newNamespace.getNamespacePrefix())) {
425                 if (newNamespace.alwaysDeclare() && !namespace.alwaysDeclare()) {
426                     // An alwaysDeclare=true trumps false.
427                     // Don't modify the existing object in the set, merely swap them.
428                     namespaces.remove(namespace);
429                     namespaces.add(newNamespace);
430                     return;
431                 } else {
432                     // URI and prefix match, alwaysDeclare does also, so just leave the original
433                     return;
434                 }
435             }
436         }
437         
438         namespaces.add(newNamespace);
439     }
440     
441     /**
442      * Remove a Namespace from a set of Namespaces.  Equivalence of Namespace instances will be based 
443      * on namespace URI and prefix only. The <code>alwaysDeclare</code> property will be ignored for
444      * purpose of equivalence.
445      * 
446      * @param namespaces the set of namespaces
447      * @param oldNamespace the namespace to add to the set
448      */
449     private void removeNamespace(Set<Namespace> namespaces, Namespace oldNamespace) {
450         if (oldNamespace == null) {
451             return;
452         }
453         
454         Iterator<Namespace> iter = namespaces.iterator();
455         while (iter.hasNext()) {
456             Namespace namespace = iter.next();
457             if (DatatypeHelper.safeEquals(namespace.getNamespaceURI(), oldNamespace.getNamespaceURI()) &&
458                     DatatypeHelper.safeEquals(namespace.getNamespacePrefix(), oldNamespace.getNamespacePrefix())) {
459                 iter.remove();
460             }
461         }
462         
463     }
464     
465     /**
466      * Merge 2 or more Namespace collections into a single set, with equivalence semantics as described
467      * in {@link #addNamespace(Set, Namespace)}.
468      * 
469      * @param namespaces list of Namespaces to merge
470      * @return the a new set of merged Namespaces
471      */
472     private Set<Namespace> mergeNamespaceCollections(Collection<Namespace> ... namespaces) {
473         LazySet<Namespace> newNamespaces = new LazySet<Namespace>();
474         
475         for (Collection<Namespace> nsCollection : namespaces) {
476             for (Namespace ns : nsCollection) {
477                 if (ns != null) {
478                     addNamespace(newNamespaces, ns);
479                 }
480             }
481         }
482         
483         return newNamespaces;
484     }
485     
486     /**
487      * Get the set of namespaces which are currently visibly-used on the owning XMLObject (only the owner,
488      * not its children).
489      * 
490      * <p>
491      * Namespaces returned in the set are copied from the ones held in the manager.  The
492      * <code>alwaysDeclare</code> property is not preserved.
493      * </p>
494      * 
495      * @return the set of visibly-used namespaces
496      */
497     private Set<Namespace> getVisibleNamespaces() {
498         LazySet<Namespace> namespaces = new LazySet<Namespace>();
499 
500         // Add namespace from element name.
501         if (getElementNameNamespace() != null) {
502             namespaces.add(copyNamespace(getElementNameNamespace()));
503         }
504 
505         // Add xsi attribute prefix, if element carries an xsi:type.
506         if (getElementTypeNamespace() != null) {
507             namespaces.add(copyNamespace(XSI_NAMESPACE));
508         }
509         
510         // Add namespaces from attribute names
511         for (Namespace attribName : attrNames) {
512             if (attribName != null) {
513                 namespaces.add(copyNamespace(attribName));
514             }
515         }
516 
517         return namespaces;
518     }
519 
520     /**
521      * Get the set of non-visibly used namespaces used on the owning XMLObject (only the owner,
522      * not the owner's children).
523      * 
524      * <p>
525      * Namespaces returned in the set are copied from the ones held in the manager.  The
526      * <code>alwaysDeclare</code> property is not preserved.
527      * </p>
528      * 
529      * @return the set of non-visibly-used namespaces
530      */
531     private Set<Namespace> getNonVisibleNamespaceCandidates() {
532         LazySet<Namespace> namespaces = new LazySet<Namespace>();
533 
534         // Add xsi:type value's prefix, if element carries an xsi:type
535         if (getElementTypeNamespace() != null) {
536             namespaces.add(copyNamespace(getElementTypeNamespace()));
537         }
538         
539         // Add prefixes from attribute and content values
540         for (Namespace attribValue : attrValues.values()) {
541             if (attribValue != null) {
542                 namespaces.add(copyNamespace(attribValue));
543             }
544         }
545         if (contentValue != null) {
546             namespaces.add(copyNamespace(contentValue));
547         }
548 
549         return namespaces;
550     }
551     
552     /**
553      * Get a copy of a Namespace.  The <code>alwaysDeclare</code> property is not preserved.
554      * 
555      * @param orig the namespace instance to copy
556      * @return a copy of the specified namespace
557      */
558     private Namespace copyNamespace(Namespace orig) {
559         if (orig == null) {
560             return null;
561         } else {
562             return new Namespace(orig.getNamespaceURI(), orig.getNamespacePrefix());
563         }
564     }
565     
566     /**
567      * Add the prefixes from a collection of namespaces to a set of prefixes. The 
568      * value used to represent the default namespace will be normalized to {@link NamespaceManager#DEFAULT_NS_TOKEN}.
569      * 
570      * @param prefixes the set of prefixes to which to add
571      * @param namespaces the source set of Namespaces
572      */
573     private void addPrefixes(Set<String> prefixes, Collection<Namespace> namespaces) {
574         for (Namespace ns : namespaces) {
575             String prefix = DatatypeHelper.safeTrimOrNullString(ns.getNamespacePrefix());
576             if (prefix == null) {
577                 prefix = DEFAULT_NS_TOKEN;
578             }
579             prefixes.add(prefix);
580         }
581     }
582     
583     /**
584      * Check whether the supplied QName contains non-empty namespace info and should
585      * be managed by the namespace manager.
586      * 
587      * @param name the QName to check
588      * @return true if the QName contains non-empty namespace info and should be managed, false otherwise
589      */
590     private boolean checkQName(QName name) {
591         return !DatatypeHelper.isEmpty(name.getNamespaceURI());
592     }
593     
594 }