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 }