org.javelin.sws.ext.bind.SweJaxbMarshaller.java Source code

Java tutorial

Introduction

Here is the source code for org.javelin.sws.ext.bind.SweJaxbMarshaller.java

Source

/*
 * Copyright 2005-2013 the original author or authors.
 *
 * 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 org.javelin.sws.ext.bind;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.MarshalException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.attachment.AttachmentMarshaller;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;

import org.javelin.sws.ext.bind.internal.MarshallingContext;
import org.javelin.sws.ext.bind.internal.model.TypedPattern;
import org.javelin.sws.ext.bind.internal.model.ElementPattern;
import org.javelin.sws.ext.bind.internal.model.XmlEventsPattern;
import org.javelin.sws.ext.bind.internal.stax.IndentingXMLEventWriter;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;

/**
 * <p>Marshaller able to convert objects to XML representations. This marshaller can marshal any object, given the object is:<ul>
 * <li>a {@link JAXBElement}</li>
 * <li>an object of class annotated with {@link XmlRootElement} annotations</li>
 * </ul></p>
 *
 * @author Grzegorz Grzybek
 */
public class SweJaxbMarshaller implements Marshaller, SweJaxbConstants {

    private SweJaxbContext jaxbContext;

    private Map<String, Object> properties = new HashMap<String, Object>();

    private AttachmentMarshaller attachmentMarshaller;

    private Listener listener;

    private Schema schema;

    private Map<Class<?>, XmlAdapter<?, ?>> adapters = new HashMap<Class<?>, XmlAdapter<?, ?>>();

    private ValidationEventHandler validationEventHandler;

    private XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();

    /* properties-based configuration of marshaller */

    private boolean formatting = false;

    private boolean fragment = false;

    private String encoding = "UTF-8";

    private boolean multiRefEncoding = false;

    private boolean sendTypes = false;

    /**
     * @param jaxbContext
     */
    SweJaxbMarshaller(SweJaxbContext jaxbContext) {
        this.jaxbContext = jaxbContext;
    }

    /*
     * Marshal operations.
     * 
     * One marshal() method can invoke another. The "ultimate" marshal() method should (I think) take XMLEventWriter as an argument.
     * The other methods construct or acquire proper XMLEventWriter object.
     * 
     * XMLEvent added() to XMLEventWriter is at the higher level than invocation of XMLStreamWriter methods. That's why Woodstox passes
     * XMLStreamWriter to XMLEventWriter. We need bridge classes like e.g., javanet.staxutils.XMLStreamEventWriter (see: http://java.net/projects/stax-utils/pages/Utilities).
     * Some cases are provided in org.springframework.util.xml.StaxUtils - but not all of them.
     */

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, javax.xml.transform.Result)
     */
    @Override
    public void marshal(Object jaxbElement, Result result) throws JAXBException {
        try {
            XMLEventWriter writer = this.xmlOutputFactory.createXMLEventWriter(result);
            this.marshal(jaxbElement, writer);
            writer.flush();
        } catch (XMLStreamException e) {
            throw new JAXBException(e.getMessage(), e);
        }
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, java.io.OutputStream)
     */
    @Override
    public void marshal(Object jaxbElement, OutputStream os) throws JAXBException {
        this.marshal(jaxbElement, new StreamResult(os));
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, java.io.File)
     */
    @Override
    public void marshal(Object jaxbElement, File output) throws JAXBException {
        try {
            this.marshal(jaxbElement, new BufferedOutputStream(new FileOutputStream(output)));
        } catch (FileNotFoundException e) {
            throw new JAXBException(e);
        }
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, java.io.Writer)
     */
    @Override
    public void marshal(Object jaxbElement, Writer writer) throws JAXBException {
        this.marshal(jaxbElement, new StreamResult(writer));
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, org.xml.sax.ContentHandler)
     */
    @Override
    public void marshal(Object jaxbElement, ContentHandler handler) throws JAXBException {
        this.marshal(jaxbElement, new SAXResult(handler));
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, org.w3c.dom.Node)
     */
    @Override
    public void marshal(Object jaxbElement, Node node) throws JAXBException {
        this.marshal(jaxbElement, new DOMResult(node));
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, javax.xml.stream.XMLStreamWriter)
     */
    @Override
    public void marshal(Object jaxbElement, XMLStreamWriter writer) throws JAXBException {
        this.marshal(jaxbElement, new StAXResult(writer));
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#marshal(java.lang.Object, javax.xml.stream.XMLEventWriter)
     */
    @Override
    public void marshal(Object jaxbElement, XMLEventWriter writer) throws JAXBException {
        this.marshal0(jaxbElement, writer);
    }

    /* Other operations */

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#getNode(java.lang.Object)
     */
    @Override
    public Node getNode(Object contentTree) throws JAXBException {
        throw new UnsupportedOperationException("Why would anyone want to do this (at least now)?");
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#setProperty(java.lang.String, java.lang.Object)
     */
    @Override
    public void setProperty(String name, Object value) throws PropertyException {
        if (Marshaller.JAXB_FORMATTED_OUTPUT.equals(name))
            this.formatting = (Boolean) value;
        else if (Marshaller.JAXB_FRAGMENT.equals(name))
            this.fragment = (Boolean) value;
        else if (Marshaller.JAXB_ENCODING.equals(name))
            this.encoding = (String) value;
        else if (SweJaxbConstants.SWE_MARSHALLER_PROPERTY_JAXB_MULTIREFS.equals(name))
            this.multiRefEncoding = (Boolean) value;
        else if (SweJaxbConstants.SWE_MARSHALLER_PROPERTY_SEND_TYPES.equals(name))
            this.sendTypes = (Boolean) value;
        else
            this.properties.put(name, value);
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#getProperty(java.lang.String)
     */
    @Override
    public Object getProperty(String name) throws PropertyException {
        return this.properties.get(name);
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#setEventHandler(javax.xml.bind.ValidationEventHandler)
     */
    @Override
    public void setEventHandler(ValidationEventHandler handler) throws JAXBException {
        this.validationEventHandler = handler;
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#getEventHandler()
     */
    @Override
    public ValidationEventHandler getEventHandler() throws JAXBException {
        return this.validationEventHandler;
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#setAdapter(javax.xml.bind.annotation.adapters.XmlAdapter)
     */
    @Override
    @SuppressWarnings("rawtypes")
    public void setAdapter(XmlAdapter adapter) {
        throw new UnsupportedOperationException(
                "What class do you want to set this adapter for? Probably not for " + adapter.getClass() + "...");
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#setAdapter(java.lang.Class, javax.xml.bind.annotation.adapters.XmlAdapter)
     */
    @Override
    @SuppressWarnings("rawtypes")
    public <A extends XmlAdapter> void setAdapter(Class<A> type, A adapter) {
        this.adapters.put(type, adapter);
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#getAdapter(java.lang.Class)
     */
    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public <A extends XmlAdapter> A getAdapter(Class<A> type) {
        return (A) this.adapters.get(type);
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#setAttachmentMarshaller(javax.xml.bind.attachment.AttachmentMarshaller)
     */
    @Override
    public void setAttachmentMarshaller(AttachmentMarshaller am) {
        this.attachmentMarshaller = am;
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#getAttachmentMarshaller()
     */
    @Override
    public AttachmentMarshaller getAttachmentMarshaller() {
        return this.attachmentMarshaller;
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#setSchema(javax.xml.validation.Schema)
     */
    @Override
    public void setSchema(Schema schema) {
        this.schema = schema;
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#getSchema()
     */
    @Override
    public Schema getSchema() {
        return this.schema;
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#setListener(javax.xml.bind.Marshaller.Listener)
     */
    @Override
    public void setListener(Listener listener) {
        this.listener = listener;
    }

    /* (non-Javadoc)
     * @see javax.xml.bind.Marshaller#getListener()
     */
    @Override
    public Listener getListener() {
        return this.listener;
    }

    /* Internal methods */

    /**
     * The main marshal method which converts an object into a series of {@link XMLEventWriter XML events} being added to {@link XMLEventWriter}.
     * 
     * @param jaxbElement
     * @param writer
     */
    private void marshal0(Object jaxbElement, XMLEventWriter writer) throws JAXBException {
        ElementPattern<?> pattern = this.determineXmlPattern(jaxbElement);
        try {
            if (this.formatting)
                writer = new IndentingXMLEventWriter(writer);

            if (!this.fragment)
                writer.add(XmlEventsPattern.XML_EVENTS_FACTORY.createStartDocument(this.encoding, "1.0", true));

            MarshallingContext context = new MarshallingContext();
            // TODO: correctly handle internal xmlOutputFactory's IS_REPAIRING_NAMESPACES property!
            context.setRepairingXmlEventWriter(
                    (Boolean) this.xmlOutputFactory.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES));
            context.setMultiRefEncoding(this.multiRefEncoding);
            context.setSendTypes(this.sendTypes);

            // go!
            pattern.replay(jaxbElement, writer, context);

            if (context.isMultiRefEncoding())
                context.getMultiRefSupport().outputMultiRefs(writer, context);

            if (!this.fragment)
                writer.add(XmlEventsPattern.XML_EVENTS_FACTORY.createEndDocument());
        } catch (XMLStreamException e) {
            throw new MarshalException(e);
        }
    }

    /**
     * Using the class of the object to be marshalled determines a series of XML Events to be generated in order to marshal given object.
     * Used only at the highest level of marshalling and must result in a pattern which {@link XmlEventsPattern#isElement() is an element}.
     * 
     * @param jaxbElement
     * @return
     * @throws MarshalException 
     */
    private ElementPattern<?> determineXmlPattern(Object jaxbElement) throws MarshalException {
        Class<?> clz = jaxbElement.getClass();
        if (jaxbElement instanceof JAXBElement)
            clz = ((JAXBElement<?>) jaxbElement).getDeclaredType();

        // pattern (both for root and non-root classes) must be present in context's mapping metadata
        TypedPattern<?> pattern = this.jaxbContext.patterns.get(clz);

        if (pattern == null)
            throw new MarshalException("Unable to determine XML Events pattern to marshal object of class "
                    + jaxbElement.getClass().getName());

        // if we marshal XmlRootElement, then return proper pattern
        if (this.jaxbContext.rootPatterns.containsKey(clz))
            return this.jaxbContext.rootPatterns.get(clz);

        // if we marshal JAXBElement, we create ElementPattern on demand (without caching it!)
        if (jaxbElement instanceof JAXBElement) {
            // TODO: use ((JAXBElement<?>) jaxbElement).getDeclaredType() or ((JAXBElement<?>) jaxbElement).getValue().getClass()?
            return ElementPattern.newElementPattern(((JAXBElement<?>) jaxbElement).getName(), pattern);
        }

        throw new MarshalException("Unable to marshal object of class " + clz.getName()
                + ". Only JAXBElements and @XmlRootElement annotated classes may be marshalled.");
    }

}