View Javadoc

1   /*
2    * Copyright [2005] [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.Collections;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Set;
23  
24  import javax.xml.namespace.QName;
25  
26  import org.opensaml.xml.util.DatatypeHelper;
27  import org.opensaml.xml.util.IDIndex;
28  import org.opensaml.xml.util.XMLHelper;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  import org.w3c.dom.Element;
32  
33  /**
34   * An abstract implementation of XMLObject.
35   */
36  public abstract class AbstractXMLObject implements XMLObject {
37  
38      /** Class logger. */
39      private final Logger log = LoggerFactory.getLogger(AbstractXMLObject.class);
40  
41      /** Parent of this element. */
42      private XMLObject parent;
43  
44      /** The name of this element with namespace and prefix information. */
45      private QName elementQname;
46  
47      /** Schema locations for this XML object. */
48      private String schemaLocation;
49  
50      /** No-namespace schema locations for this XML object. */
51      private String noNamespaceSchemaLocation;
52  
53      /** The schema type of this element with namespace and prefix information. */
54      private QName typeQname;
55  
56      /** Namespaces declared on this element. */
57      private Set<Namespace> namespaces;
58  
59      /** DOM Element representation of this object. */
60      private Element dom;
61  
62      /**
63       * Mapping of ID attributes to XMLObjects in the subtree rooted at this object. This allows constant-time
64       * dereferencing of ID-typed attributes within the subtree.
65       */
66      private final IDIndex idIndex;
67  
68      /**
69       * Constructor.
70       * 
71       * @param namespaceURI the namespace the element is in
72       * @param elementLocalName the local name of the XML element this Object represents
73       * @param namespacePrefix the prefix for the given namespace
74       */
75      protected AbstractXMLObject(String namespaceURI, String elementLocalName, String namespacePrefix) {
76          idIndex = new IDIndex(this);
77          namespaces = new HashSet<Namespace>();
78          elementQname = XMLHelper.constructQName(namespaceURI, elementLocalName, namespacePrefix);
79          addNamespace(new Namespace(namespaceURI, namespacePrefix));
80          setElementNamespacePrefix(namespacePrefix);
81      }
82      
83      /** {@inheritDoc} */
84      public void addNamespace(Namespace namespace) {
85          if (namespace != null) {
86              namespaces.add(namespace);
87          }
88      }
89  
90      /** {@inheritDoc} */
91      public void detach(){
92          releaseParentDOM(true);
93          parent = null;
94      }
95  
96      /** {@inheritDoc} */
97      public Element getDOM() {
98          return dom;
99      }
100 
101     /** {@inheritDoc} */
102     public QName getElementQName() {
103         return new QName(elementQname.getNamespaceURI(), elementQname.getLocalPart(), elementQname.getPrefix());
104     }
105 
106     /** {@inheritDoc} */
107     public IDIndex getIDIndex() {
108         return idIndex;
109     }
110 
111     /** {@inheritDoc} */
112     public Set<Namespace> getNamespaces() {
113         return Collections.unmodifiableSet(namespaces);
114     }
115 
116     /** {@inheritDoc} */
117     public String getNoNamespaceSchemaLocation() {
118         return noNamespaceSchemaLocation;
119     }
120 
121     /**
122      * Gets the parent of this element.
123      * 
124      * @return the parent of this element
125      */
126     public XMLObject getParent() {
127         return parent;
128     }
129 
130     /** {@inheritDoc} */
131     public String getSchemaLocation() {
132         return schemaLocation;
133     }
134 
135     /** {@inheritDoc} */
136     public QName getSchemaType() {
137         return typeQname;
138     }
139 
140     /** {@inheritDoc} */
141     public boolean hasChildren() {
142         List<? extends XMLObject> children = getOrderedChildren();
143         return children != null && children.size() > 0;
144     }
145 
146     /** {@inheritDoc} */
147     public boolean hasParent() {
148         return getParent() != null;
149     }
150 
151     /**
152      * A helper function for derived classes. This checks for semantic equality between two QNames if it they are
153      * different invalidates the DOM. It returns the normalized value so subclasses just have to go. this.foo =
154      * prepareForAssignment(this.foo, foo);
155      * 
156      * @param oldValue - the current value
157      * @param newValue - the new value
158      * 
159      * @return the value that should be assigned
160      */
161     protected QName prepareForAssignment(QName oldValue, QName newValue) {
162         if (oldValue == null) {
163             if (newValue != null) {
164                 Namespace newNamespace = new Namespace(newValue.getNamespaceURI(), newValue.getPrefix());
165                 addNamespace(newNamespace);
166                 releaseThisandParentDOM();
167                 return newValue;
168             } else {
169                 return null;
170             }
171         }
172 
173         if (!oldValue.equals(newValue)) {
174             if (newValue != null) {
175                 Namespace newNamespace = new Namespace(newValue.getNamespaceURI(), newValue.getPrefix());
176                 addNamespace(newNamespace);
177             }
178             releaseThisandParentDOM();
179         }
180 
181         return newValue;
182     }
183 
184     /**
185      * A helper function for derived classes. This 'nornmalizes' newString and then if it is different from oldString
186      * invalidates the DOM. It returns the normalized value so subclasses just have to go. this.foo =
187      * prepareForAssignment(this.foo, foo);
188      * 
189      * @param oldValue - the current value
190      * @param newValue - the new value
191      * 
192      * @return the value that should be assigned
193      */
194     protected String prepareForAssignment(String oldValue, String newValue) {
195         String newString = DatatypeHelper.safeTrimOrNullString(newValue);
196 
197         if (!DatatypeHelper.safeEquals(oldValue, newString)) {
198             releaseThisandParentDOM();
199         }
200 
201         return newString;
202     }
203 
204     /**
205      * A helper function for derived classes that checks to see if the old and new value are equal and if so releases
206      * the cached dom. Derived classes are expected to use this thus: <code>
207      *   this.foo = prepareForAssignment(this.foo, foo);
208      *   </code>
209      * 
210      * This method will do a (null) safe compare of the objects and will also invalidate the DOM if appropriate
211      * 
212      * @param <T> - type of object being compared and assigned
213      * @param oldValue - current value
214      * @param newValue - proposed new value
215      * 
216      * @return The value to assign to the saved Object.
217      */
218     protected <T extends Object> T prepareForAssignment(T oldValue, T newValue) {
219         if (oldValue == null) {
220             if (newValue != null) {
221                 releaseThisandParentDOM();
222                 return newValue;
223             } else {
224                 return null;
225             }
226         }
227 
228         if (!oldValue.equals(newValue)) {
229             releaseThisandParentDOM();
230         }
231 
232         return newValue;
233     }
234 
235     /**
236      * A helper function for derived classes, similar to assignString, but for (singleton) SAML objects. It is
237      * indifferent to whether either the old or the new version of the value is null. Derived classes are expected to
238      * use this thus: <code>
239      *   this.foo = prepareForAssignment(this.foo, foo);
240      *   </code>
241      * 
242      * This method will do a (null) safe compare of the objects and will also invalidate the DOM if appropriate
243      * 
244      * @param <T> type of object being compared and assigned
245      * @param oldValue current value
246      * @param newValue proposed new value
247      * 
248      * @return The value to assign to the saved Object.
249      */
250     protected <T extends XMLObject> T prepareForAssignment(T oldValue, T newValue) {
251 
252         if (newValue != null && newValue.hasParent()) {
253             throw new IllegalArgumentException(newValue.getClass().getName()
254                     + " cannot be added - it is already the child of another SAML Object");
255         }
256 
257         if (oldValue == null) {
258             if (newValue != null) {
259                 releaseThisandParentDOM();
260                 newValue.setParent(this);
261                 idIndex.registerIDMappings(newValue.getIDIndex());
262                 return newValue;
263 
264             } else {
265                 return null;
266             }
267         }
268 
269         if (!oldValue.equals(newValue)) {
270             oldValue.setParent(null);
271             releaseThisandParentDOM();
272             idIndex.deregisterIDMappings(oldValue.getIDIndex());
273             if (newValue != null) {
274                 newValue.setParent(this);
275                 idIndex.registerIDMappings(newValue.getIDIndex());
276             }
277         }
278 
279         return newValue;
280     }
281 
282     /**
283      * A helper function for derived classes. The mutator/setter method for any ID-typed attributes should call this
284      * method in order to handle getting the old value removed from the ID-to-XMLObject mapping, and the new value added
285      * to the mapping.
286      * 
287      * @param oldID the old value of the ID-typed attribute
288      * @param newID the new value of the ID-typed attribute
289      */
290     protected void registerOwnID(String oldID, String newID) {
291         String newString = DatatypeHelper.safeTrimOrNullString(newID);
292 
293         if (!DatatypeHelper.safeEquals(oldID, newString)) {
294             if (oldID != null) {
295                 idIndex.deregisterIDMapping(oldID);
296             }
297 
298             if (newString != null) {
299                 idIndex.registerIDMapping(newString, this);
300             }
301         }
302     }
303 
304     /** {@inheritDoc} */
305     public void releaseChildrenDOM(boolean propagateRelease) {
306         log.trace("Releasing cached DOM reprsentation for children of {} with propagation set to {}",
307                 getElementQName(), propagateRelease);
308         if (getOrderedChildren() != null) {
309             for (XMLObject child : getOrderedChildren()) {
310                 if (child != null) {
311                     child.releaseDOM();
312                     if (propagateRelease) {
313                         child.releaseChildrenDOM(propagateRelease);
314                     }
315                 }
316             }
317         }
318     }
319 
320     /** {@inheritDoc} */
321     public void releaseDOM() {
322         log.trace("Releasing cached DOM reprsentation for {}", getElementQName());
323         setDOM(null);
324     }
325 
326     /** {@inheritDoc} */
327     public void releaseParentDOM(boolean propagateRelease) {
328         log.trace("Releasing cached DOM reprsentation for parent of {} with propagation set to {}", getElementQName(),
329                 propagateRelease);
330         XMLObject parentElement = getParent();
331         if (parentElement != null) {
332             parent.releaseDOM();
333             if (propagateRelease) {
334                 parent.releaseParentDOM(propagateRelease);
335             }
336         }
337     }
338 
339     /**
340      * A convience method that is equal to calling {@link #releaseDOM()} then {@link #releaseChildrenDOM(boolean)} with
341      * the release being propogated.
342      */
343     public void releaseThisAndChildrenDOM() {
344         if (getDOM() != null) {
345             releaseDOM();
346             releaseChildrenDOM(true);
347         }
348     }
349 
350     /**
351      * A convience method that is equal to calling {@link #releaseDOM()} then {@link #releaseParentDOM(boolean)} with
352      * the release being propogated.
353      */
354     public void releaseThisandParentDOM() {
355         if (getDOM() != null) {
356             releaseDOM();
357             releaseParentDOM(true);
358         }
359     }
360 
361     /** {@inheritDoc} */
362     public void removeNamespace(Namespace namespace) {
363         namespaces.remove(namespace);
364     }
365 
366     /** {@inheritDoc} */
367     public XMLObject resolveID(String id) {
368         return idIndex.lookup(id);
369     }
370 
371     /** {@inheritDoc} */
372     public XMLObject resolveIDFromRoot(String id) {
373         XMLObject root = this;
374         while (root.hasParent()) {
375             root = root.getParent();
376         }
377         return root.resolveID(id);
378     }
379 
380     /** {@inheritDoc} */
381     public void setDOM(Element newDom) {
382         dom = newDom;
383     }
384 
385     /**
386      * Sets the prefix for this element's namespace.
387      * 
388      * @param prefix the prefix for this element's namespace
389      */
390     public void setElementNamespacePrefix(String prefix) {
391         if (prefix == null) {
392             elementQname = new QName(elementQname.getNamespaceURI(), elementQname.getLocalPart());
393         } else {
394             elementQname = new QName(elementQname.getNamespaceURI(), elementQname.getLocalPart(), prefix);
395         }
396     }
397 
398     /**
399      * Sets the element QName.
400      * 
401      * @param elementQName the element's QName
402      */
403     protected void setElementQName(QName elementQName) {
404         this.elementQname = XMLHelper.constructQName(elementQName.getNamespaceURI(), elementQName.getLocalPart(),
405                 elementQName.getPrefix());
406         addNamespace(new Namespace(elementQName.getNamespaceURI(), elementQName.getLocalPart()));
407     }
408 
409     /** {@inheritDoc} */
410     public void setNoNamespaceSchemaLocation(String location) {
411         noNamespaceSchemaLocation = DatatypeHelper.safeTrimOrNullString(location);
412     }
413 
414     /** {@inheritDoc} */
415     public void setParent(XMLObject newParent) {
416         parent = newParent;
417     }
418 
419     /** {@inheritDoc} */
420     public void setSchemaLocation(String location) {
421         schemaLocation = DatatypeHelper.safeTrimOrNullString(location);
422     }
423 
424     /**
425      * Sets a given QName as the schema type for the Element represented by this XMLObject. This will add the namespace
426      * to the list of namespaces scoped for this XMLObject. It will not remove any namespaces, for example, if there is
427      * already a schema type set and null is passed in.
428      * 
429      * @param type the schema type
430      */
431     protected void setSchemaType(QName type) {
432         if (type == null) {
433             typeQname = null;
434         } else {
435             typeQname = type;
436             addNamespace(new Namespace(type.getNamespaceURI(), type.getPrefix()));
437         }
438     }
439 
440 }