gov.nih.nci.cagrid.opensaml.SAMLObject.java Source code

Java tutorial

Introduction

Here is the source code for gov.nih.nci.cagrid.opensaml.SAMLObject.java

Source

/*
 *  Copyright 2001-2005 Internet2
 * 
 * Licensed 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 gov.nih.nci.cagrid.opensaml;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

/**
 *  Abstract base class for all SAML constructs
 *
 * @author     Scott Cantor
 * @created    November 17, 2001
 */
public abstract class SAMLObject implements Cloneable {
    /** OpenSAML configuration */
    protected SAMLConfig config = SAMLConfig.instance();

    /**  Root node of a DOM tree capturing the object */
    protected Node root = null;

    /** Class-specific logging object */
    protected Logger log = Logger.getLogger(this.getClass());

    /** Back pointer to SAML "parent" to allow back-walking and prevent double-containment */
    protected SAMLObject parentObject = null;

    /** Dirty bit triggers recreation of DOM */
    protected boolean dirty = true;

    /**
     *  Sets or clears the object's dirty bit. When set, serialization will flush
     *  an existing DOM. All parent objects will also be flagged.
     * 
     * @param flag  The new value of the dirty bit
     */
    protected void setDirty(boolean flag) {
        dirty = flag;
        if (flag && parentObject != null)
            parentObject.setDirty(flag);
    }

    /**
     *  Allows parsing of objects from a stream of XML
     *
     * @param  in                   A stream containing XML
     * @return                      The root of the XML document instance
     * @exception  SAMLException  Raised if an exception occurs while constructing
     *                              the object
     */
    static protected Element fromStream(InputStream in) throws SAMLException {
        try {
            Document doc = XML.parserPool.parse(in);
            return doc.getDocumentElement();
        } catch (Exception e) {
            NDC.push("fromStream");
            Logger.getLogger(SAMLObject.class.getName())
                    .error("caught an exception while parsing a stream:\n" + e.getMessage());
            NDC.pop();
            throw new MalformedException("SAMLObject.fromStream() caught exception while parsing a stream", e);
        }
    }

    /**
     *  Allows parsing of objects of a particular minor version from a stream of XML
     *
     * @param  in                   A stream containing XML
     * @param  minor                The minor version of the incoming object
     * @return                      The root of the XML document instance
     * @exception  SAMLException  Raised if an exception occurs while constructing
     *                              the object
     */
    static protected Element fromStream(InputStream in, int minor) throws SAMLException {
        try {
            Document doc = XML.parserPool.parse(new InputSource(in),
                    (minor == 1) ? XML.parserPool.getSchemaSAML11() : XML.parserPool.getSchemaSAML10());
            return doc.getDocumentElement();
        } catch (Exception e) {
            NDC.push("fromStream");
            Logger.getLogger(SAMLObject.class.getName())
                    .error("caught an exception while parsing a stream:\n" + e.getMessage());
            NDC.pop();
            throw new MalformedException("SAMLObject.fromStream() caught exception while parsing a stream", e);
        }
    }

    /**
     *  Installs the root node of this DOM as the document element
     *
     * @return    The root node so planted
     */
    protected Node plantRoot() {
        if (root != null) {
            Node domroot = root;
            while (domroot.getParentNode() != null && domroot.getParentNode().getNodeType() != Node.DOCUMENT_NODE)
                domroot = domroot.getParentNode();
            Element e = root.getOwnerDocument().getDocumentElement();
            if (e != null && e != domroot)
                root.getOwnerDocument().replaceChild(domroot, e);
            else if (e == null)
                root.getOwnerDocument().appendChild(domroot);
        }
        return root;
    }

    /**
     *  Delegates the process of building the root element of an object and
     *  inserting appropriate namespaces.
     * 
     * @param doc       The document context to use
     * @param xmlns     Include namespace(s) on root element?
     * @return  A new root element for the object 
     */
    protected abstract Element buildRoot(Document doc, boolean xmlns);

    /**
     *  Evaluates the object's content to see if it is currently valid if serialized.
     *  Does not evaluate embedded objects except on the basis of whether they exist.
     *  For example, an Assertion must have a Statement, but if an invalid statement
     *  is added, SAMLAssertion.checkValidity() would succeed, while SAMLStatement.checkValidity
     *  would raise an exception.
     * 
     * @exception   SAMLException      Raised if the serialized object would be invalid SAML,
     *      excluding any embedded objects
     */
    public abstract void checkValidity() throws SAMLException;

    /**
     *  Informs the object that it is being inserted into a composite structure, allowing
     *  it to check for existing containment and throw an exception, preventing unexplained
     *  errors due to multiple object containment.
     * 
     * @param parent    The object into which this object is being inserted
     * @return          A reference to the object being inserted (allows for cleaner insertion)
     * @throws SAMLException    Raised if this object already has a parent
     */
    public SAMLObject setParent(SAMLObject parent) throws SAMLException {
        if (parentObject != null)
            throw new SAMLException("SAMLObject.setParent() called on an already-contained object");
        if (parent == null)
            throw new IllegalArgumentException("SAMLObject.setParent() called with null parameter");
        parentObject = parent;
        return this;
    }

    /**
     *  Returns the containing object, if any. Multiple containment of a single object
     *  is prohibited! You must clone() to add an owned object to another parent.
     * 
     * @return  The parent SAML object, or null if stand-alone
     */
    public SAMLObject getParent() {
        return parentObject;
    }

    /**
     *  Initialization of an object from a DOM element
     *
     * @param  e                   Root element of a DOM tree
     * @exception  SAMLException   Raised if an exception occurs while constructing
     *                              the object
     */
    public void fromDOM(Element e) throws SAMLException {
        if (e == null)
            throw new MalformedException("SAMLObject.fromDOM() given an empty DOM");
        root = e;
        setDirty(false); // we have to be clean if we're starting from existing XML
    }

    /**
     *  Serializes the XML representation of the SAML object to a stream
     *
     * @param  out                      Stream to use for output
     * @exception  java.io.IOException  Raised if an I/O problem is detected
     * @exception  SAMLException Raised if the object is incompletely defined 
     */
    public void toStream(OutputStream out) throws java.io.IOException, SAMLException {
        try {
            toDOM();
            plantRoot();
            Canonicalizer c = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
            out.write(c.canonicalizeSubtree(root,
                    config.getProperty("gov.nih.nci.cagrid.opensaml.inclusive-namespace-prefixes")));
        } catch (InvalidCanonicalizerException e) {
            throw new java.io.IOException(e.getMessage());
        } catch (CanonicalizationException e) {
            throw new java.io.IOException(e.getMessage());
        }
    }

    /**
     *  Returns a base64-encoded XML representation of the SAML object
     *
     * @return                          A byte array containing the encoded data
     * @exception  java.io.IOException  Raised if an I/O problem is detected
     * @exception  SAMLException Raised if the object is incompletely defined
     */
    public byte[] toBase64() throws java.io.IOException, SAMLException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        toStream(out);
        return Base64.encodeBase64Chunked(out.toByteArray());
    }

    /**
     *  Transforms the object into a DOM tree using an existing document context
     *
     * @param  doc               A Document object to use in manufacturing the tree
     * @param  xmlns             Include namespace(s) on root element?
     * @return                   Root element node of the DOM tree capturing the object
     * @exception  SAMLException Raised if the object is incompletely defined
     */
    public Node toDOM(Document doc, boolean xmlns) throws SAMLException {
        checkValidity();
        if (root != null) {
            if (!dirty) {
                // If the DOM tree is already generated, compare the Documents.
                if (root.getOwnerDocument() != doc) {
                    // We already built a tree. Just adopt it into the new document.
                    return root = doc.adoptNode(root);
                }
                return root;
            }
            // Dirty, so we need a new root element.
            log.debug("toDOM() detected object changes, rebuilding tree");
        }
        return root = buildRoot(doc, xmlns);
    }

    /**
     *  Transforms the object into a DOM tree without an existing document context
     *
      * @param  xmlns             Include namespace(s) on root element?
     * @return                   Root element node of the DOM tree capturing the object
     * @exception  SAMLException Raised if the object is incompletely defined
     */
    public Node toDOM(boolean xmlns) throws SAMLException {
        // Reuse document if possible.
        if (root != null)
            return toDOM(root.getOwnerDocument(), xmlns);

        // No existing document object, so we have to create one.
        return toDOM(XML.parserPool.newDocument(), xmlns);
    }

    /**
     *  Transforms the object into a DOM tree using an existing document context,
     *  including namespace declarations
     *
     * @param  doc               A Document object to use in manufacturing the tree
     * @return                   Root element node of the DOM tree capturing the object
     * @exception  SAMLException Raised if the object is incompletely defined
     */
    public Node toDOM(Document doc) throws SAMLException {
        return toDOM(doc, true);
    }

    /**
     *  Transforms the object into a DOM tree without an existing document context,
     *  including namespace declarations
     *
     * @return                   Root element node of the DOM tree capturing the object
     * @exception  SAMLException Raised if the object is incompletely defined
     */
    public Node toDOM() throws SAMLException {
        return toDOM(true);
    }

    /**
     *  Copies a SAML object such that no dependencies exist between the original
     *  and the copy.
     * 
     * @return      The new object
     * @see java.lang.Object#clone()
     */
    protected Object clone() throws CloneNotSupportedException {
        SAMLObject dup = (SAMLObject) super.clone();

        // Clear the DOM and parent before returning the copy.
        dup.root = null;
        dup.parentObject = null;
        dup.dirty = true;

        return dup;
    }

    /**
     *  Serializes a SAML object to a string in exclusive canonical form.
     * 
     * @return      The canonicalized output
     * @see java.lang.Object#toString()
     */
    public String toString() {
        try {
            // We already support serialization to streams, but note that c14n XML is always in UTF-8.
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            toStream(os);
            return os.toString("UTF8");
        } catch (java.io.IOException e) {
            NDC.push("toString");
            log.error("caught an I/O exception while serializing XML: " + e);
            NDC.pop();
            return "";
        } catch (SAMLException e) {
            NDC.push("toString");
            log.error("caught a SAML exception while serializing XML: " + e);
            NDC.pop();
            return "";
        }
    }
}