org.plasma.sdo.xml.StreamMarshaller.java Source code

Java tutorial

Introduction

Here is the source code for org.plasma.sdo.xml.StreamMarshaller.java

Source

/**
 *         PlasmaSDO License
 * 
 * This is a community release of PlasmaSDO, a dual-license 
 * Service Data Object (SDO) 2.1 implementation. 
 * This particular copy of the software is released under the 
 * version 2 of the GNU General Public License. PlasmaSDO was developed by 
 * TerraMeta Software, Inc.
 * 
 * Copyright (c) 2013, TerraMeta Software, Inc. All rights reserved.
 * 
 * General License information can be found below.
 * 
 * This distribution may include materials developed by third
 * parties. For license and attribution notices for these
 * materials, please refer to the documentation that accompanies
 * this distribution (see the "Licenses for Third-Party Components"
 * appendix) or view the online documentation at 
 * <http://plasma-sdo.org/licenses/>.
 *  
 */
package org.plasma.sdo.xml;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import javanet.staxutils.IndentingXMLStreamWriter;

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.plasma.sdo.PlasmaDataGraphEventVisitor;
import org.plasma.sdo.PlasmaDataObject;
import org.plasma.sdo.PlasmaNode;
import org.plasma.sdo.PlasmaProperty;
import org.plasma.sdo.PlasmaType;
import org.plasma.sdo.helper.DataConverter;
import org.plasma.sdo.helper.PlasmaTypeHelper;
import org.plasma.sdo.helper.PlasmaXSDHelper;
import org.plasma.sdo.profile.KeyType;
import org.plasma.xml.schema.SchemaConstants;
import org.plasma.xml.schema.SchemaUtil;

import commonj.sdo.DataObject;
import commonj.sdo.Property;
import commonj.sdo.Type;
import commonj.sdo.helper.XMLDocument;

/**
 * A Streaming API for XML (StAX) based XML marshaler which converts/writes 
 * an SDO data graph to various supplied (stream-based) XML output sources. As the data graph
 * is traversed to generate output, containment and non containment reference 
 * (property) associations are detected and handled such that even though 
 * multiple references to the same data object are encountered, (where possible) no duplicate
 * XML data is written to the result. This not only reduces the size of the XML result, but
 * provides enough information related to containment and non containment references
 * such that the original data graph can be coalesced and re-constituted from the XML
 * back into a fully operational state.     
 * 
 * Of primary concern with regard to handling containment and non containment 
 * references is of course that the XML result be valid in relation to the associated 
 * XML Schema. XML Schema Instance (XSI) mechanisms are used to accommodate multiple XML
 * representations of the same (SDO) Type under both containment and non containment
 * scenarios.     
 *     
 */
//FIXME: deal with change-summaries
//FIXME: deal with metadata
public class StreamMarshaller extends Marshaller {

    private static Log log = LogFactory.getFactory().getInstance(StreamMarshaller.class);
    private XMLOutputFactory factory;
    private String namespacePrefix = "tns";
    private PlasmaXSDHelper helper = PlasmaXSDHelper.INSTANCE;
    private PropertyComparator comparator = new PropertyComparator();

    /**
     * Constructor. 
     * @param document the document containing the root data object
     * and other XML related values
     * @param options the XML marshaling options
     * @see XMLDocument
     */
    public StreamMarshaller(XMLDocument document) {
        super(MarshallerFlavor.STAX, document);
        construct();
    }

    /**
     * Constructor. 
     * @param document the document containing the root data object
     * and other XML related values
     * @param options the XML marshaling options
     * @see XMLDocument
     * @see XMLOptions
     */
    public StreamMarshaller(XMLDocument document, XMLOptions options) {
        super(document, options);
        construct();
    }

    private void construct() {
        this.factory = XMLOutputFactory.newInstance();
        // Set namespace prefix defaulting for all created writers
        //this.factory.setProperty("javax.xml.stream.isPrefixDefaulting",Boolean.TRUE);
    }

    private void setup() {
        if (this.getOptions() != null && this.getOptions().getRootNamespacePrefix() != null
                && this.getOptions().getRootNamespacePrefix().length() > 0)
            namespacePrefix = this.getOptions().getRootNamespacePrefix();

    }

    public void marshal(OutputStream stream) throws XMLStreamException, MarshallerException {
        String encoding = findEncoding();
        XMLStreamWriter writer = null;
        if (encoding != null)
            writer = factory.createXMLStreamWriter(stream, encoding);
        else
            writer = factory.createXMLStreamWriter(stream);

        if (isPrettyPrint())
            writer = new IndentingXMLStreamWriter(writer);

        try {
            write(writer);
        } finally {
            writer.close();
        }
    }

    public void marshal(Writer outputWriter) throws XMLStreamException, MarshallerException {
        XMLStreamWriter writer = factory.createXMLStreamWriter(outputWriter);

        writer = new IndentingXMLStreamWriter(writer);
        try {
            write(writer);
        } finally {
            writer.close();
        }
    }

    public void marshal(Result outputResult) throws XMLStreamException, MarshallerException {
        XMLStreamWriter writer = factory.createXMLStreamWriter(outputResult);

        writer = new IndentingXMLStreamWriter(writer);
        try {
            write(writer);
        } finally {
            writer.close();
        }
    }

    private void writeRootAttributes(XMLStreamWriter writer) throws XMLStreamException {
        writer.writeAttribute("xmlns", this.document.getRootElementURI(), namespacePrefix,
                this.document.getRootElementURI());
        writer.writeAttribute("xmlns", SchemaConstants.XMLSCHEMA_NAMESPACE_URI, "xs",
                SchemaConstants.XMLSCHEMA_NAMESPACE_URI);
        writer.writeAttribute("xmlns", XMLConstants.XMLSCHEMA_INSTANCE_NAMESPACE_URI, "xsi",
                XMLConstants.XMLSCHEMA_INSTANCE_NAMESPACE_URI);
        if (this.document.getSchemaLocation() != null)
            writer.writeAttribute("xsi", XMLConstants.XMLSCHEMA_INSTANCE_NAMESPACE_URI, "schemaLocation",
                    this.document.getSchemaLocation());
    }

    private String findEncoding() {
        if (this.document.getEncoding() != null)
            return this.document.getEncoding();
        else if (this.getOptions() != null && this.getOptions().getEncoding() != null)
            return this.getOptions().getEncoding();
        else
            return null;
    }

    private boolean isPrettyPrint() {
        if (this.getOptions() != null)
            return this.getOptions().isPrettyPrint();
        else
            return true;
    }

    private void write(XMLStreamWriter writer) throws XMLStreamException {
        setup();

        if (this.document.isXMLDeclaration()) {
            String encoding = findEncoding();
            if (encoding != null)
                writer.writeStartDocument(encoding, this.document.getXMLVersion());
            else
                writer.writeStartDocument(this.document.getXMLVersion());
        }

        // Write a processing instruction
        //writer.writeProcessingInstruction(
        //     "xml-stylesheet href='catalog.xsl' type='text/xsl'");   

        if (this.document.getRootElementName() != null) {
            //writer.writeDefaultNamespace(this.document.getRootElementURI());                   
            writer.writeStartElement(namespacePrefix, this.document.getRootElementName(),
                    this.document.getRootElementURI());
            writeRootAttributes(writer);
        }

        EventVisitor visitor = new EventVisitor(writer);
        ((PlasmaDataObject) this.document.getRootObject()).accept(visitor);

        if (this.document.getRootElementName() != null) {
            writer.writeEndElement();
        }

        writer.writeEndDocument();
    }

    protected String fromObject(Type sourceType, Object value) {
        return DataConverter.INSTANCE.toString(sourceType, value);
    }

    class EventVisitor implements PlasmaDataGraphEventVisitor {

        private XMLStreamWriter writer;
        private HashSet<PlasmaDataObject> nonContainmentObjects = new HashSet<PlasmaDataObject>();

        public EventVisitor(XMLStreamWriter writer) {
            this.writer = writer;
        }

        public void start(PlasmaDataObject target, PlasmaDataObject source, String sourcePropertyName, int level) {
            try {
                PlasmaType sourceType = null;
                PlasmaProperty sourceProperty = null;
                if (source != null) {
                    if (this.nonContainmentObjects.contains(source)) {
                        this.nonContainmentObjects.add(target); // so gets checked as source at next level, removed on end event
                        return; // no content needed for non containment obj
                    }
                    sourceType = (PlasmaType) source.getType();
                    sourceProperty = (PlasmaProperty) sourceType.getProperty(sourcePropertyName);
                }

                PlasmaType targetType = (PlasmaType) target.getType();

                if (log.isDebugEnabled())
                    if (source == null)
                        log.debug("start: " + targetType.getName() + "(" + ((PlasmaNode) target).getUUIDAsString()
                                + ")");
                    else
                        log.debug("start: " + sourceType.getName() + "(" + ((PlasmaNode) source).getUUIDAsString()
                                + ")." + sourceProperty.getName() + "->" + targetType.getName() + "("
                                + ((PlasmaNode) target).getUUIDAsString() + ")");
                if (source != null) {
                    this.writer.writeStartElement(helper.getLocalName(sourceProperty));
                } else { // its the root
                    if (document.getRootElementName() == null) {
                        // no passed in root element name, write namespace
                        // stuff into start tag of data-graph root
                        this.writer.writeStartElement(namespacePrefix, helper.getLocalName(targetType),
                                document.getRootElementURI());
                        writeRootAttributes(writer);
                    } else {
                        this.writer.writeStartElement(helper.getLocalName(targetType));
                    }
                }
                // root or containment reference
                if (source == null || source.contains(target)) {
                    writeContent(this.writer, target, source, sourceProperty, targetType, sourceType, level);
                } else {
                    // source node does not contain the target, yet the
                    // target may contain other nodes which we will subsequently
                    // get, Therefore check for source as non-containment obj above
                    this.nonContainmentObjects.add(target);
                    writeNonContainmentReferenceContent(this.writer, target, source, sourceProperty, targetType,
                            sourceType, level);
                }

            } catch (XMLStreamException e) {
                throw new MarshallerRuntimeException(e);
            } catch (IOException e) {
                throw new MarshallerRuntimeException(e);
            }
        }

        public void end(PlasmaDataObject target, PlasmaDataObject source, String sourcePropertyName, int level) {
            try {
                if (source != null) {
                    if (this.nonContainmentObjects.contains(source)) {
                        this.nonContainmentObjects.remove(target); // 
                        return; // no content needed for non containment obj
                    }
                }
                this.writer.writeEndElement();
            } catch (XMLStreamException e) {
                throw new MarshallerRuntimeException(e);
            }
        }
    }

    private void writeNonContainmentReferenceContent(XMLStreamWriter writer, DataObject dataObject,
            DataObject source, Property sourceProperty, PlasmaType targetType, PlasmaType sourceType, int level)
            throws IOException, XMLStreamException {

        // create XSI type for all non-containment refs
        writer.writeAttribute("xsi", XMLConstants.XMLSCHEMA_INSTANCE_NAMESPACE_URI, "type",
                namespacePrefix + ":" + SchemaUtil.getNonContainmentReferenceName(targetType));

        writer.writeAttribute(SchemaUtil.getSerializationAttributeName(),
                ((PlasmaDataObject) dataObject).getUUIDAsString());
    }

    private void writeContent(XMLStreamWriter writer, DataObject dataObject, DataObject source,
            Property sourceProperty, PlasmaType targetType, PlasmaType sourceType, int level)
            throws IOException, XMLStreamException {

        int externKeyCount = 0;
        for (Property property : targetType.getProperties()) {
            PlasmaProperty prop = (PlasmaProperty) property;
            if (prop.isKey(KeyType.external)) {
                externKeyCount++;
            }
        }

        // create XSI type on demand for containment refs
        // FIXME: SDO namespaces are necessary in some cases
        // to determine exact XSI type to unmarshal. Can't determine
        // this from the property type on unmarshalling. 
        if (externKeyCount > 0)
            writer.writeAttribute("xsi", XMLConstants.XMLSCHEMA_INSTANCE_NAMESPACE_URI, "type",
                    namespacePrefix + ":" + SchemaUtil.getContainmentReferenceName(targetType));

        writer.writeAttribute(SchemaUtil.getSerializationAttributeName(),
                ((PlasmaDataObject) dataObject).getUUIDAsString());

        for (Property property : targetType.getProperties()) {
            PlasmaProperty prop = (PlasmaProperty) property;
            if (!prop.getType().isDataType() || !prop.isXMLAttribute()) {
                continue;
            }
            // FIXME - what about pri-keys which are not sequences
            //VisibilityKind visibility = (VisibilityKind)prop.get(PlasmaProperty.INSTANCE_PROPERTY_OBJECT_VISIBILITY);
            //if (visibility != null && visibility.ordinal() == VisibilityKind.private_.ordinal())
            //   continue; // for properties defined as private no XML or XML Schema property generated
            Object value = dataObject.get(prop);
            if (value == null)
                continue;
            writer.writeAttribute(helper.getLocalName(prop), fromObject(prop.getType(), value));
        }

        // add element properties
        List<Property> list = targetType.getProperties();
        PlasmaProperty[] properties = new PlasmaProperty[list.size()];
        list.toArray(properties);
        Arrays.sort(properties, this.comparator);
        for (Property property : properties) {
            PlasmaProperty prop = (PlasmaProperty) property;
            if (!prop.getType().isDataType() || prop.isXMLAttribute())
                continue;
            // FIXME - what about pri-keys which are not sequences
            //VisibilityKind visibility = (VisibilityKind)prop.get(PlasmaProperty.INSTANCE_PROPERTY_OBJECT_VISIBILITY);
            //if (visibility != null && visibility.ordinal() == VisibilityKind.private_.ordinal())
            //   continue; // for properties defined as private no XML or XML Schema property generated
            Object value = dataObject.get(prop);
            if (value == null)
                continue;
            writer.writeStartElement(helper.getLocalName(prop));
            writer.writeCharacters(this.fromObject(prop.getType(), value));
            writer.writeEndElement();
        }
    }

    class PropertyComparator implements Comparator<PlasmaProperty> {
        public int compare(PlasmaProperty p1, PlasmaProperty p2) {
            if (p1.getSort() != null && p2.getSort() != null)
                return p1.getSort().getKey().compareTo(p2.getSort().getKey());
            else
                return p1.getName().compareTo(p2.getName());
        }
    }

}