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

org.plasma.sdo.xml.StreamMarshaller Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/**
 *         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 
 * .
 *  
 */
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.HashSet;
import java.util.List;

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.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;
import javanet.staxutils.IndentingXMLStreamWriter;


/**
 * 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 nonContainmentObjects = new HashSet();
		
		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 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 {
		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());
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy