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

org.opensaml.xml.util.AttributeMap 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.

There is a newer version: 1.4.2
Show 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.util;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;

import net.jcip.annotations.NotThreadSafe;

import org.opensaml.xml.Configuration;
import org.opensaml.xml.NamespaceManager;
import org.opensaml.xml.XMLObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A map of attribute names and attribute values that invalidates the DOM of the attribute owning XMLObject when the
 * attributes change.
 * 
 * Note: 
 */
@NotThreadSafe
public class AttributeMap implements Map {
    
    /** Logger. */
    private final Logger log = LoggerFactory.getLogger(AttributeMap.class);

    /** XMLObject owning the attributes. */
    private XMLObject attributeOwner;

    /** Map of attributes. */
    private Map attributes;
    
    /** Set of attribute QNames which have been locally registered as having an ID type within this 
     * AttributeMap instance. */
    private Set idAttribNames;
    
    /** Set of attribute QNames which have been locally registered as having an QName value type within this 
     * AttributeMap instance. */
    private Set qnameAttribNames;
    
    /** Flag indicating whether an attempt should be made to infer QName values, 
     * if attribute is not registered as a QName type. */
    private boolean inferQNameValues;

    /**
     * Constructor.
     *
     * @param newOwner the XMLObject that owns these attributes
     * 
     * @throws NullPointerException thrown if the given XMLObject is null
     */
    public AttributeMap(XMLObject newOwner) throws NullPointerException {
        if (newOwner == null) {
            throw new NullPointerException("Attribute owner XMLObject may not be null");
        }

        attributeOwner = newOwner;
        attributes = new LazyMap();
        idAttribNames = new LazySet();
        qnameAttribNames = new LazySet();
    }

    /** {@inheritDoc} */
    public String put(QName attributeName, String value) {
        String oldValue = get(attributeName);
        if (value != oldValue) {
            releaseDOM();
            attributes.put(attributeName, value);
            if (isIDAttribute(attributeName) || Configuration.isIDAttribute(attributeName)) {
                attributeOwner.getIDIndex().deregisterIDMapping(oldValue);
                attributeOwner.getIDIndex().registerIDMapping(value, attributeOwner);
            }
            if (!DatatypeHelper.isEmpty(attributeName.getNamespaceURI())) {
                if (value == null) {
                    attributeOwner.getNamespaceManager().deregisterAttributeName(attributeName);
                } else {
                    attributeOwner.getNamespaceManager().registerAttributeName(attributeName);
                }
            }
            checkAndDeregisterQNameValue(attributeName, oldValue);
            checkAndRegisterQNameValue(attributeName, value);
        }
        
        return oldValue;
    }
    
    /**
     * Set an attribute value as a QName.  This method takes care of properly registering and 
     * deregistering the namespace information associated with the new QName being added, and
     * with the old QName being possibly removed.
     * 
     * @param attributeName the attribute name
     * @param value the QName attribute value
     * @return the old attribute value, possibly null
     */
    public QName put(QName attributeName, QName value) {
        String oldValueString = get(attributeName);
        
        QName oldValue = null;
        if (!DatatypeHelper.isEmpty(oldValueString)) {
            oldValue = resolveQName(oldValueString, true);
        }
        
        if (!DatatypeHelper.safeEquals(oldValue, value)) {
            releaseDOM();
            if (value != null) {
                // new value is not null, old value was either null or non-equal
                String newStringValue = constructAttributeValue(value);
                attributes.put(attributeName, newStringValue);
                registerQNameValue(attributeName, value);
                attributeOwner.getNamespaceManager().registerAttributeName(attributeName);
            } else {
                // new value is null, old value was not null
                deregisterQNameValue(attributeName);
                attributeOwner.getNamespaceManager().deregisterAttributeName(attributeName);
            }
        }
        
        return oldValue;
    }

    /** {@inheritDoc} */
    public void clear() {
        LazySet keys = new LazySet();
        keys.addAll(attributes.keySet());
        for (QName attributeName : keys) {
            remove(attributeName);
        }
    }

    /**
     * Returns the set of keys.
     * 
     * @return unmodifiable set of keys
     */
    public Set keySet() {
        return Collections.unmodifiableSet(attributes.keySet());
    }

    /** {@inheritDoc} */
    public int size() {
        return attributes.size();
    }

    /** {@inheritDoc} */
    public boolean isEmpty() {
        return attributes.isEmpty();
    }

    /** {@inheritDoc} */
    public boolean containsKey(Object key) {
        return attributes.containsKey(key);
    }

    /** {@inheritDoc} */
    public boolean containsValue(Object value) {
        return attributes.containsValue(value);
    }

    /** {@inheritDoc} */
    public String get(Object key) {
        return attributes.get(key);
    }

    /** {@inheritDoc} */
    public String remove(Object key) {
        String removedValue = attributes.remove(key);
        if (removedValue != null) {
            releaseDOM();
            QName attributeName = (QName) key;
            if (isIDAttribute(attributeName) || Configuration.isIDAttribute(attributeName)) {
                attributeOwner.getIDIndex().deregisterIDMapping(removedValue);
            }
            attributeOwner.getNamespaceManager().deregisterAttributeName(attributeName);
            checkAndDeregisterQNameValue(attributeName, removedValue);
        }

        return removedValue;
    }

    /** {@inheritDoc} */
    public void putAll(Map t) {
        if (t != null && t.size() > 0) {
            for (Entry entry : t.entrySet()) {
                put(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * Returns the values in this map.
     * 
     * @return an unmodifiable collection of values
     */
    public Collection values() {
        return Collections.unmodifiableCollection(attributes.values());
    }

    /**
     * Returns the set of entries.
     * 
     * @return unmodifiable set of entries
     */
    public Set> entrySet() {
        return Collections.unmodifiableSet(attributes.entrySet());
    }
    
    /**
     * Register an attribute as having a type of ID.
     * 
     * @param attributeName the QName of the ID attribute to be registered
     */
    public void registerID(QName attributeName) {
        if (! idAttribNames.contains(attributeName)) {
            idAttribNames.add(attributeName);
        }
        
        // In case attribute already has a value,
        // register the current value mapping with the XMLObject owner.
        if (containsKey(attributeName)) {
            attributeOwner.getIDIndex().registerIDMapping(get(attributeName), attributeOwner);
        }
    }
    
    /**
     * Deregister an attribute as having a type of ID.
     * 
     * @param attributeName the QName of the ID attribute to be de-registered
     */
    public void deregisterID(QName attributeName) {
        if (idAttribNames.contains(attributeName)) {
            idAttribNames.remove(attributeName);
        }
        
        // In case attribute already has a value,
        // deregister the current value mapping with the XMLObject owner.
        if (containsKey(attributeName)) {
            attributeOwner.getIDIndex().deregisterIDMapping(get(attributeName));
        }
    }
    
    /**
     * Check whether a given attribute is locally registered as having an ID type within
     * this AttributeMap instance.
     * 
     * @param attributeName the QName of the attribute to be checked for ID type.
     * @return true if attribute is registered as having an ID type.
     */
    public boolean isIDAttribute(QName attributeName) {
        return idAttribNames.contains(attributeName);
    }
    
    /**
     * Register an attribute as having a type of QName.
     * 
     * @param attributeName the name of the QName-valued attribute to be registered
     */
    public void registerQNameAttribute(QName attributeName) {
        qnameAttribNames.add(attributeName);
    }
    
    /**
     * Deregister an attribute as having a type of QName.
     * 
     * @param attributeName the name of the QName-valued attribute to be registered
     */
    public void deregisterQNameAttribute(QName attributeName) {
        qnameAttribNames.remove(attributeName);
    }
    
    /**
     * Check whether a given attribute is known to have a QName type.
     * 
     * @param attributeName the QName of the attribute to be checked for QName type.
     * @return true if attribute is registered as having an QName type.
     */
    public boolean isQNameAttribute(QName attributeName) {
        return qnameAttribNames.contains(attributeName);
    }
    
    /**
     * Get the flag indicating whether an attempt should be made to infer QName values, 
     * if attribute is not registered via a configuration as a QName type. Default is false.
     * 
     * @return true if QName types should be inferred, false if not
     * 
     */
    public boolean isInferQNameValues() {
        return inferQNameValues;
    }
    
    /**
     * Set the flag indicating whether an attempt should be made to infer QName values, 
     * if attribute is not registered via a configuration as a QName type. Default is false.
     * 
     * @param flag true if QName types should be inferred, false if not
     * 
     */
    public void setInferQNameValues(boolean flag) {
        inferQNameValues = flag;
    }
    
    /**
     * Releases the DOM caching associated XMLObject and its ancestors.
     */
    private void releaseDOM() {
        attributeOwner.releaseDOM();
        attributeOwner.releaseParentDOM(true);
    }
    
    /**
     * Check whether the attribute value is a QName type, and if it is,
     * register it with the owner's namespace manger.
     * 
     * @param attributeName the attribute name
     * @param attributeValue the attribute value
     */
    private void checkAndRegisterQNameValue(QName attributeName, String attributeValue) {
        if (attributeValue == null) {
            return;
        }
        
        QName qnameValue = checkQName(attributeName, attributeValue);
        if (qnameValue != null) {
            log.trace("Attribute '{}' with value '{}' was evaluated to be QName type", 
                    attributeName, attributeValue);
            registerQNameValue(attributeName, qnameValue);
        } else {
            log.trace("Attribute '{}' with value '{}' was not evaluated to be QName type", 
                    attributeName, attributeValue);
        }
        
    }
    
    /**
     * Register a QName attribute value with the owner's namespace manger.
     * 
     * @param attributeName the attribute name
     * @param attributeValue the attribute value
     */
    private void registerQNameValue(QName attributeName, QName attributeValue) {
        if (attributeValue == null) {
            return;
        }
        
        String attributeID = NamespaceManager.generateAttributeID(attributeName);
        log.trace("Registering QName attribute value '{}' under attibute ID '{}'",
                attributeValue, attributeID);
        attributeOwner.getNamespaceManager().registerAttributeValue(attributeID, attributeValue);
    }
    
    /**
     * Check whether the attribute value is a QName type, and if it is,
     * deregister it with the owner's namespace manger.
     * 
     * @param attributeName the attribute name
     * @param attributeValue the attribute value
     */
    private void checkAndDeregisterQNameValue(QName attributeName, String attributeValue) {
        if (attributeValue == null) {
            return;
        }
        
        QName qnameValue = checkQName(attributeName, attributeValue);
        if (qnameValue != null) {
            log.trace("Attribute '{}' with value '{}' was evaluated to be QName type", 
                    attributeName, attributeValue);
            deregisterQNameValue(attributeName);
        } else {
            log.trace("Attribute '{}' with value '{}' was not evaluated to be QName type", 
                    attributeName, attributeValue);
        }
    }
    
    /**
     * Deregister a QName attribute value with the owner's namespace manger.
     * 
     * @param attributeName the attribute name whose QName attribute value should be deregistered
     */
    private void deregisterQNameValue(QName attributeName) {
        String attributeID = NamespaceManager.generateAttributeID(attributeName);
        log.trace("Deregistering QName attribute with attibute ID '{}'", attributeID);
        attributeOwner.getNamespaceManager().deregisterAttributeValue(attributeID);
    }
    
    /**
     * Check where the attribute value is a QName type, and if so, return the QName.
     * 
     * @param attributeName the attribute name
     * @param attributeValue the attribute value
     * @return the QName if the attribute value is a QName type, otherwise null
     */
    private QName checkQName(QName attributeName, String attributeValue) {
        log.trace("Checking whether attribute '{}' with value {} is a QName type", attributeName, attributeValue);
        
        if (attributeValue == null) {
            log.trace("Attribute value was null, returning null");
            return null;
        }
        
        if (isQNameAttribute(attributeName)) {
            log.trace("Configuration indicates attribute with name '{}' is a QName type, resolving value QName", 
                    attributeName);
            // Do support the default namespace in this scenario, since we know it should be a QName
            QName valueName = resolveQName(attributeValue, true);
            if (valueName != null) {
                log.trace("Successfully resolved attribute value to QName: {}", valueName);
            } else {
                log.trace("Could not resolve attribute value to QName, returning null");
            }
            return valueName;
        } else if (isInferQNameValues()) {
            log.trace("Attempting to infer whether attribute value is a QName");
            // Do not support the default namespace in this scenario, since we're trying to infer.
            // Better to fail to resolve than to infer a bogus QName value.
            QName valueName = resolveQName(attributeValue, false);
            if (valueName != null) {
                log.trace("Resolved attribute as a QName: '{}'", valueName);
            } else {
                log.trace("Attribute value was not resolveable to a QName, returning null");
            }
            return valueName;
        } else {
            log.trace("Attribute was not registered in configuration as a QName type and QName inference is disabled");
            return null;
        }

    }
    
    /**
     * Attempt to resolve the specified attribute value into a QName.
     * 
     * @param attributeValue the value to evaluate
     * @param isDefaultNSOK flag indicating whether resolution should be attempted if the prefix is null, 
     *           that is, the value is considered to be be potentially in the default XML namespace
     * 
     * @return the QName, or null if unable to resolve into a QName
     */
    private QName resolveQName(String attributeValue, boolean isDefaultNSOK) {
        if (attributeValue == null) {
            return null;
        }
        log.trace("Attemtping to resolve QName from attribute value '{}'", attributeValue);
        
        // Attempt to resolve value as a QName by splitting on colon and then attempting to resolve
        // this candidate prefix into a namespace URI. 
        String candidatePrefix = null;
        String localPart = null;
        int ci = attributeValue.indexOf(':');
        if (ci > -1) {
            candidatePrefix = attributeValue.substring(0, ci);
            log.trace("Evaluating candiate namespace prefix '{}'", candidatePrefix);
            localPart = attributeValue.substring(ci+1);
        } else {
            // No prefix - possibly evaluate as if in the default namespace
            if (isDefaultNSOK) {
                candidatePrefix = null;
                log.trace("Value did not contain a colon, evaluating as default namespace");
                localPart = attributeValue;
            } else {
                log.trace("Value did not contain a colon, default namespace is disallowed, returning null");
                return null;
            }
        }
        
        log.trace("Evaluated QName local part as '{}'", localPart);
        
        String nsURI = XMLObjectHelper.lookupNamespaceURI(attributeOwner, candidatePrefix);
        log.trace("Resolved namespace URI '{}'", nsURI);
        if (nsURI != null) {
            QName name = XMLHelper.constructQName(nsURI, localPart, candidatePrefix);
            log.trace("Resolved QName '{}'", name);
            return name;
        } else {
            log.trace("Namespace URI for candidate prefix '{}' could not be resolved", candidatePrefix);
        }
        
        log.trace("Value was either not a QName, or namespace URI could not be resolved");
        
        return null;
    }
    
    /**
     * Construct the string representation of a QName attribute value.
     * 
     * @param attributeValue the QName to process
     * @return the attribute value string representation of the QName
     */
    private String constructAttributeValue(QName attributeValue) {
        String trimmedLocalName = DatatypeHelper.safeTrimOrNullString(attributeValue.getLocalPart());

        if (trimmedLocalName == null) {
            throw new IllegalArgumentException("Local name may not be null or empty");
        }

        String qualifiedName;
        String trimmedPrefix = DatatypeHelper.safeTrimOrNullString(attributeValue.getPrefix());
        if (trimmedPrefix != null) {
            qualifiedName = trimmedPrefix + ":" + DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
        } else {
            qualifiedName = DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
        }
        return qualifiedName;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy