All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensaml.xml.AbstractXMLObject Maven / Gradle / Ivy

Go to download

XMLTooling-J is a low-level library that may be used to construct libraries that allow developers to work with XML in a Java beans manner.

The newest version!
/*
 * Licensed to the University Corporation for Advanced Internet Development, 
 * Inc. (UCAID) under one or more contributor license agreements.  See the 
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID licenses this file to You under the Apache 
 * License, Version 2.0 (the "License"); you may not use this file except in 
 * compliance with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.opensaml.xml;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.xml.namespace.QName;

import org.opensaml.xml.schema.XSBooleanValue;
import org.opensaml.xml.util.DatatypeHelper;
import org.opensaml.xml.util.IDIndex;
import org.opensaml.xml.util.XMLConstants;
import org.opensaml.xml.util.XMLHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

/**
 * An abstract implementation of XMLObject.
 */
public abstract class AbstractXMLObject implements XMLObject {

    /** Class logger. */
    private final Logger log = LoggerFactory.getLogger(AbstractXMLObject.class);

    /** Parent of this element. */
    private XMLObject parent;

    /** The name of this element with namespace and prefix information. */
    private QName elementQname;

    /** Schema locations for this XML object. */
    private String schemaLocation;

    /** No-namespace schema locations for this XML object. */
    private String noNamespaceSchemaLocation;

    /** The schema type of this element with namespace and prefix information. */
    private QName typeQname;

    /** DOM Element representation of this object. */
    private Element dom;
    
    /** The value of the xsi:nil attribute. */
    private  XSBooleanValue nil;
    
    /** The namespace manager for this XML object. */
    private NamespaceManager nsManager;

    /**
     * Mapping of ID attributes to XMLObjects in the subtree rooted at this object. This allows constant-time
     * dereferencing of ID-typed attributes within the subtree.
     */
    private final IDIndex idIndex;

    /**
     * Constructor.
     * 
     * @param namespaceURI the namespace the element is in
     * @param elementLocalName the local name of the XML element this Object represents
     * @param namespacePrefix the prefix for the given namespace
     */
    protected AbstractXMLObject(String namespaceURI, String elementLocalName, String namespacePrefix) {
        nsManager = new NamespaceManager(this);
        idIndex = new IDIndex(this);
        elementQname = XMLHelper.constructQName(namespaceURI, elementLocalName, namespacePrefix);
        if(namespaceURI != null){
            setElementNamespacePrefix(namespacePrefix);
        }
    }
    
    /** {@inheritDoc} */
    public void addNamespace(Namespace newNamespace) {
        getNamespaceManager().registerNamespace(newNamespace);
    }

    /** {@inheritDoc} */
    public void detach(){
        releaseParentDOM(true);
        parent = null;
    }

    /** {@inheritDoc} */
    public Element getDOM() {
        return dom;
    }

    /** {@inheritDoc} */
    public QName getElementQName() {
        return elementQname;
    }

    /** {@inheritDoc} */
    public IDIndex getIDIndex() {
        return idIndex;
    }
    
    /** {@inheritDoc} */
    public NamespaceManager getNamespaceManager() {
        return nsManager;
    }

    /** {@inheritDoc} */
    public Set getNamespaces() {
        return Collections.unmodifiableSet(getNamespaceManager().getNamespaces());
    }

    /** {@inheritDoc} */
    public String getNoNamespaceSchemaLocation() {
        return noNamespaceSchemaLocation;
    }

    /**
     * Gets the parent of this element.
     * 
     * @return the parent of this element
     */
    public XMLObject getParent() {
        return parent;
    }

    /** {@inheritDoc} */
    public String getSchemaLocation() {
        return schemaLocation;
    }

    /** {@inheritDoc} */
    public QName getSchemaType() {
        return typeQname;
    }

    /** {@inheritDoc} */
    public boolean hasChildren() {
        List children = getOrderedChildren();
        return children != null && children.size() > 0;
    }

    /** {@inheritDoc} */
    public boolean hasParent() {
        return getParent() != null;
    }
    
    /**
     * A helper function for derived classes.  This method should be called when the value of a
     * namespace-qualified attribute changes.
     * 
     * @param attributeName the attribute name
     * @param hasValue true to indicate that the attribute has a value, false to indicate it has no value
     */
    protected void manageQualifiedAttributeNamespace(QName attributeName, boolean hasValue) {
        if (hasValue) {
            getNamespaceManager().registerAttributeName(attributeName);
        } else {
            getNamespaceManager().deregisterAttributeName(attributeName);
        }
    }

    /**
     * A helper function for derived classes. This checks for semantic equality between two QNames if it they are
     * different invalidates the DOM. It returns the normalized value so subclasses just have to go. this.foo =
     * prepareForAssignment(this.foo, foo);
     * 
     * @param oldValue - the current value
     * @param newValue - the new value
     * 
     * @return the value that should be assigned
     * 
     * @deprecated replacement {@link #prepareAttributeValueForAssignment(String, QName, QName)} 
     *                or {@link #prepareElementContentForAssignment(QName, QName)} as appropriate
     */
    protected QName prepareForAssignment(QName oldValue, QName newValue) {
        if (oldValue == null) {
            if (newValue != null) {
                Namespace newNamespace = new Namespace(newValue.getNamespaceURI(), newValue.getPrefix());
                addNamespace(newNamespace);
                releaseThisandParentDOM();
                return newValue;
            } else {
                return null;
            }
        }

        if (!oldValue.equals(newValue)) {
            if (newValue != null) {
                Namespace newNamespace = new Namespace(newValue.getNamespaceURI(), newValue.getPrefix());
                addNamespace(newNamespace);
            }
            releaseThisandParentDOM();
        }

        return newValue;
    }
    
    /**
     * A helper function for derived classes. This checks for semantic equality between two QNames if it they are
     * different invalidates the DOM. It returns the normalized value so subclasses just have to go. this.foo =
     * prepareElementContentForAssignment(this.foo, foo);
     * 
     * @param oldValue - the current value
     * @param newValue - the new value
     * 
     * @return the value that should be assigned
     */
    protected QName prepareElementContentForAssignment(QName oldValue, QName newValue) {
        if (oldValue == null) {
            if (newValue != null) {
                getNamespaceManager().registerContentValue(newValue);
                releaseThisandParentDOM();
                return newValue;
            } else {
                return null;
            }
        }
        
        // Old value was not null, so go ahead and deregister it
        getNamespaceManager().deregisterContentValue();

        if (!oldValue.equals(newValue)) {
            if (newValue != null) {
                getNamespaceManager().registerContentValue(newValue);
            }
            releaseThisandParentDOM();
        }

        return newValue;
    }
    
    
    /**
     * A helper function for derived classes. This checks for semantic equality between two QNames if it they are
     * different invalidates the DOM. It returns the normalized value so subclasses just have to go. this.foo =
     * prepareAttributeValueForAssignment(this.foo, foo);
     * 
     * @param attributeID - unique identifier of the attribute in the content model within this XMLObject, used to 
     *        identify the attribute within the XMLObject's NamespaceManager
     * @param oldValue - the current value
     * @param newValue - the new value
     * 
     * @return the value that should be assigned
     */
    protected QName prepareAttributeValueForAssignment(String attributeID, QName oldValue, QName newValue) {
        if (oldValue == null) {
            if (newValue != null) {
                getNamespaceManager().registerAttributeValue(attributeID, newValue);
                releaseThisandParentDOM();
                return newValue;
            } else {
                return null;
            }
        }
        
        // Old value was not null, so go ahead and deregister it
        getNamespaceManager().deregisterAttributeValue(attributeID);

        if (!oldValue.equals(newValue)) {
            if (newValue != null) {
                getNamespaceManager().registerAttributeValue(attributeID, newValue);
            }
            releaseThisandParentDOM();
        }

        return newValue;
    }

    /**
     * A helper function for derived classes. This 'nornmalizes' newString and then if it is different from oldString
     * invalidates the DOM. It returns the normalized value so subclasses just have to go. this.foo =
     * prepareForAssignment(this.foo, foo);
     * 
     * @param oldValue - the current value
     * @param newValue - the new value
     * 
     * @return the value that should be assigned
     */
    protected String prepareForAssignment(String oldValue, String newValue) {
        String newString = DatatypeHelper.safeTrimOrNullString(newValue);

        if (!DatatypeHelper.safeEquals(oldValue, newString)) {
            releaseThisandParentDOM();
        }

        return newString;
    }

    /**
     * A helper function for derived classes that checks to see if the old and new value are equal and if so releases
     * the cached dom. Derived classes are expected to use this thus: 
     *   this.foo = prepareForAssignment(this.foo, foo);
     *   
     * 
     * This method will do a (null) safe compare of the objects and will also invalidate the DOM if appropriate
     * 
     * @param  - type of object being compared and assigned
     * @param oldValue - current value
     * @param newValue - proposed new value
     * 
     * @return The value to assign to the saved Object.
     */
    protected  T prepareForAssignment(T oldValue, T newValue) {
        if (oldValue == null) {
            if (newValue != null) {
                releaseThisandParentDOM();
                return newValue;
            } else {
                return null;
            }
        }

        if (!oldValue.equals(newValue)) {
            releaseThisandParentDOM();
        }

        return newValue;
    }

    /**
     * A helper function for derived classes, similar to assignString, but for (singleton) SAML objects. It is
     * indifferent to whether either the old or the new version of the value is null. Derived classes are expected to
     * use this thus: 
     *   this.foo = prepareForAssignment(this.foo, foo);
     *   
     * 
     * This method will do a (null) safe compare of the objects and will also invalidate the DOM if appropriate
     * 
     * @param  type of object being compared and assigned
     * @param oldValue current value
     * @param newValue proposed new value
     * 
     * @return The value to assign to the saved Object.
     */
    protected  T prepareForAssignment(T oldValue, T newValue) {

        if (newValue != null && newValue.hasParent()) {
            throw new IllegalArgumentException(newValue.getClass().getName()
                    + " cannot be added - it is already the child of another SAML Object");
        }

        if (oldValue == null) {
            if (newValue != null) {
                releaseThisandParentDOM();
                newValue.setParent(this);
                idIndex.registerIDMappings(newValue.getIDIndex());
                return newValue;

            } else {
                return null;
            }
        }

        if (!oldValue.equals(newValue)) {
            oldValue.setParent(null);
            releaseThisandParentDOM();
            idIndex.deregisterIDMappings(oldValue.getIDIndex());
            if (newValue != null) {
                newValue.setParent(this);
                idIndex.registerIDMappings(newValue.getIDIndex());
            }
        }

        return newValue;
    }

    /**
     * A helper function for derived classes. The mutator/setter method for any ID-typed attributes should call this
     * method in order to handle getting the old value removed from the ID-to-XMLObject mapping, and the new value added
     * to the mapping.
     * 
     * @param oldID the old value of the ID-typed attribute
     * @param newID the new value of the ID-typed attribute
     */
    protected void registerOwnID(String oldID, String newID) {
        String newString = DatatypeHelper.safeTrimOrNullString(newID);

        if (!DatatypeHelper.safeEquals(oldID, newString)) {
            if (oldID != null) {
                idIndex.deregisterIDMapping(oldID);
            }

            if (newString != null) {
                idIndex.registerIDMapping(newString, this);
            }
        }
    }

    /** {@inheritDoc} */
    public void releaseChildrenDOM(boolean propagateRelease) {
        log.trace("Releasing cached DOM reprsentation for children of {} with propagation set to {}",
                getElementQName(), propagateRelease);
        if (getOrderedChildren() != null) {
            for (XMLObject child : getOrderedChildren()) {
                if (child != null) {
                    child.releaseDOM();
                    if (propagateRelease) {
                        child.releaseChildrenDOM(propagateRelease);
                    }
                }
            }
        }
    }

    /** {@inheritDoc} */
    public void releaseDOM() {
        log.trace("Releasing cached DOM reprsentation for {}", getElementQName());
        setDOM(null);
    }

    /** {@inheritDoc} */
    public void releaseParentDOM(boolean propagateRelease) {
        log.trace("Releasing cached DOM reprsentation for parent of {} with propagation set to {}", getElementQName(),
                propagateRelease);
        XMLObject parentElement = getParent();
        if (parentElement != null) {
            parent.releaseDOM();
            if (propagateRelease) {
                parent.releaseParentDOM(propagateRelease);
            }
        }
    }

    /**
     * A convience method that is equal to calling {@link #releaseDOM()} then {@link #releaseChildrenDOM(boolean)} with
     * the release being propogated.
     */
    public void releaseThisAndChildrenDOM() {
        if (getDOM() != null) {
            releaseDOM();
            releaseChildrenDOM(true);
        }
    }

    /**
     * A convience method that is equal to calling {@link #releaseDOM()} then {@link #releaseParentDOM(boolean)} with
     * the release being propogated.
     */
    public void releaseThisandParentDOM() {
        if (getDOM() != null) {
            releaseDOM();
            releaseParentDOM(true);
        }
    }

    /** {@inheritDoc} */
    public void removeNamespace(Namespace namespace) {
        getNamespaceManager().deregisterNamespace(namespace);
    }

    /** {@inheritDoc} */
    public XMLObject resolveID(String id) {
        return idIndex.lookup(id);
    }

    /** {@inheritDoc} */
    public XMLObject resolveIDFromRoot(String id) {
        XMLObject root = this;
        while (root.hasParent()) {
            root = root.getParent();
        }
        return root.resolveID(id);
    }

    /** {@inheritDoc} */
    public void setDOM(Element newDom) {
        dom = newDom;
    }

    /**
     * Sets the prefix for this element's namespace.
     * 
     * @param prefix the prefix for this element's namespace
     */
    public void setElementNamespacePrefix(String prefix) {
        if (prefix == null) {
            elementQname = new QName(elementQname.getNamespaceURI(), elementQname.getLocalPart());
        } else {
            elementQname = new QName(elementQname.getNamespaceURI(), elementQname.getLocalPart(), prefix);
        }
        getNamespaceManager().registerElementName(elementQname);
    }

    /**
     * Sets the element QName.
     * 
     * @param elementQName the element's QName
     */
    protected void setElementQName(QName elementQName) {
        this.elementQname = XMLHelper.constructQName(elementQName.getNamespaceURI(), elementQName.getLocalPart(),
                elementQName.getPrefix());
        getNamespaceManager().registerElementName(this.elementQname);
    }

    /** {@inheritDoc} */
    public void setNoNamespaceSchemaLocation(String location) {
        noNamespaceSchemaLocation = DatatypeHelper.safeTrimOrNullString(location);
        manageQualifiedAttributeNamespace(XMLConstants.XSI_NO_NAMESPACE_SCHEMA_LOCATION_ATTRIB_NAME, schemaLocation != null);
    }

    /** {@inheritDoc} */
    public void setParent(XMLObject newParent) {
        parent = newParent;
    }

    /** {@inheritDoc} */
    public void setSchemaLocation(String location) {
        schemaLocation = DatatypeHelper.safeTrimOrNullString(location);
        manageQualifiedAttributeNamespace(XMLConstants.XSI_SCHEMA_LOCATION_ATTRIB_NAME, schemaLocation != null);
    }

    /**
     * Sets a given QName as the schema type for the Element represented by this XMLObject. This will register the namespace
     * for the type as well as for the xsi:type qualified attribute name with the namespace manager for this XMLObject.
     * If null is passed, the type name and xsi:type name will be deregistered.
     * 
     * @param type the schema type
     */
    protected void setSchemaType(QName type) {
        typeQname = type;
        getNamespaceManager().registerElementType(typeQname);
        manageQualifiedAttributeNamespace(XMLConstants.XSI_TYPE_ATTRIB_NAME, typeQname != null);
    }
    
    /** {@inheritDoc} */
    public Boolean isNil() {
        if (nil != null) {
            return nil.getValue();
        }

        return Boolean.FALSE;
    }

    /** {@inheritDoc} */
    public XSBooleanValue isNilXSBoolean() {
        return nil;
    }

    /** {@inheritDoc} */
    public void setNil(Boolean newNil) {
        if (newNil != null) {
            nil = prepareForAssignment(nil, new XSBooleanValue(newNil, false));
        } else {
            nil = prepareForAssignment(nil, null);
        }
        manageQualifiedAttributeNamespace(XMLConstants.XSI_NIL_ATTRIB_NAME, nil != null);
    }

    /** {@inheritDoc} */
    public void setNil(XSBooleanValue newNil) {
        nil = prepareForAssignment(nil, newNil);
        manageQualifiedAttributeNamespace(XMLConstants.XSI_NIL_ATTRIB_NAME, nil != null);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy