# JsonXMLBinder.java

    /*
     * Copyright 2011, 2012 Odysseus Software GmbH
     *
     * 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.apache.synapse.commons.staxon.core.json.jaxb;

    import java.io.Reader;
    import java.io.Writer;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;

    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Marshaller;
    import javax.xml.bind.Unmarshaller;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.XmlType;
    import javax.xml.stream.XMLStreamConstants;
    import javax.xml.stream.XMLStreamException;
    import javax.xml.stream.XMLStreamReader;
    import javax.xml.stream.XMLStreamWriter;

    import org.apache.synapse.commons.staxon.core.json.JsonXMLConfig;
    import org.apache.synapse.commons.staxon.core.json.JsonXMLConfigBuilder;
    import org.apache.synapse.commons.staxon.core.json.JsonXMLInputFactory;
    import org.apache.synapse.commons.staxon.core.json.JsonXMLOutputFactory;
    import org.apache.synapse.commons.staxon.core.json.JsonXMLStreamConstants;
    import org.apache.synapse.commons.staxon.core.json.util.XMLMultipleStreamWriter;

    /**
     * Read/write instances of JAXB-annotated classes from/to JSON.
     */
    public class JsonXMLBinder {
        private final JsonXMLRootProvider rootProvider;
        private final boolean writeDocumentArray;

        public JsonXMLBinder() {
            this(true);
        }

        protected JsonXMLBinder(boolean writeDocumentArray) {
            this(new JsonXMLRootProvider(), writeDocumentArray);
        }

        protected JsonXMLBinder(JsonXMLRootProvider rootProvider, boolean writeDocumentArray) {
            this.rootProvider = rootProvider;
            this.writeDocumentArray = writeDocumentArray;
        }

        private JsonXMLConfig toJsonXMLConfig(Class<?> type, JsonXML config) throws JAXBException {
            return new JsonXMLConfigBuilder().
                    autoArray(config.autoArray()).
                    autoPrimitive(config.autoPrimitive()).
                    multiplePI(true).
                    namespaceDeclarations(config.namespaceDeclarations()).
                    namespaceSeparator(config.namespaceSeparator()).
                    prettyPrint(config.prettyPrint()).
                    virtualRoot(config.virtualRoot() ? rootProvider.getName(type) : null).
                    build();
        }

        protected JsonXMLInputFactory createInputFactory(Class<?> type, JsonXML config) throws JAXBException {
            return new JsonXMLInputFactory(toJsonXMLConfig(type, config));
        }

        protected XMLStreamReader createXMLStreamReader(Class<?> type, JsonXML config, Reader stream) throws XMLStreamException, JAXBException {
            return createInputFactory(type, config).createXMLStreamReader(stream);
        }

        protected JsonXMLOutputFactory createOutputFactory(Class<?> type, JsonXML config) throws JAXBException {
            return new JsonXMLOutputFactory(toJsonXMLConfig(type, config));
        }

        protected XMLStreamWriter createXMLStreamWriter(Class<?> type, JsonXML config, Writer stream) throws XMLStreamException, JAXBException {
            XMLStreamWriter writer = createOutputFactory(type, config).createXMLStreamWriter(stream);
            if (config.multiplePaths().length > 0) {
                writer = new XMLMultipleStreamWriter(writer, !config.virtualRoot(), config.multiplePaths());
            }
            return writer;
        }

        public boolean isBindable(Class<?> type) {
            return type.isAnnotationPresent(XmlRootElement.class) || type.isAnnotationPresent(XmlType.class);
        }

        private void checkBindable(Class<?> type) throws JAXBException {
            if (!isBindable(type)) {
                throw new JAXBException("Cannot bind type: " + type.getName());
            }
        }

        protected <T> T unmarshal(Class<? extends T> type, JsonXML config, Unmarshaller unmarshaller, XMLStreamReader reader) throws JAXBException, XMLStreamException {
            if (type.isAnnotationPresent(XmlRootElement.class)) {
                return type.cast(unmarshaller.unmarshal(reader));
            } else if (type.isAnnotationPresent(XmlType.class)) {
                return unmarshaller.unmarshal(reader, type).getValue();
            } else { // good luck
                return type.cast(unmarshaller.unmarshal(reader, type));
            }
        }

        protected void marshal(Class<?> type, JsonXML config, Marshaller marshaller, XMLStreamWriter writer, Object value)
                throws JAXBException, XMLStreamException {
            Object element = null;
            if (type.isAnnotationPresent(XmlRootElement.class)) {
                element = value;
            } else if (type.isAnnotationPresent(XmlType.class)) {
                element = rootProvider.createElement(type, value);
                if (element == null) {
                    throw new JAXBException("Cannot create JAXBElement");
                }
            } else { // good luck...
                element = value;
            }
            marshaller.marshal(element, writer);
        }

        public <T> T readObject(Class<? extends T> type, JsonXML config, JAXBContext context, Reader stream)
                throws XMLStreamException, JAXBException {
            checkBindable(type);
            XMLStreamReader reader = createXMLStreamReader(type, config, stream);
            T result;
            if (reader.isCharacters() && reader.getText() == null) { // hack: read null
                result = null;
            } else {
                reader.require(XMLStreamConstants.START_DOCUMENT, null, null);
                Unmarshaller unmarshaller = context.createUnmarshaller();
                result = unmarshal(type, config, unmarshaller, reader);
                reader.require(XMLStreamConstants.END_DOCUMENT, null, null);
            }
            reader.close();
            return result;
        }

        public void writeObject(Class<?> type, JsonXML config, JAXBContext context, Writer stream, Object value)
                throws XMLStreamException, JAXBException {
            checkBindable(type);
            XMLStreamWriter writer = createXMLStreamWriter(type, config, stream);
            if (value == null) { // hack: write null
                writer.writeCharacters(null);
            } else {
                Marshaller marshaller = context.createMarshaller();
                marshal(type, config, marshaller, writer, value);
            }
            writer.close();
        }

        public <T> List<T> readArray(Class<? extends T> type, JsonXML config, JAXBContext context, Reader stream)
                throws XMLStreamException, JAXBException {
            checkBindable(type);
            XMLStreamReader reader = createXMLStreamReader(type, config, stream);
            List<T> result;
            if (reader.isCharacters() && reader.getText() == null) { // hack: read null
                result = null;
            } else {
                boolean documentArray = JsonXMLStreamConstants.MULTIPLE_PI_TARGET.equals(reader.getPITarget());
                Unmarshaller unmarshaller = context.createUnmarshaller();
                while (reader.hasNext() && !reader.isStartElement() && !reader.isCharacters()) {
                    reader.next();
                }
                result = new ArrayList<T>();
                while (reader.hasNext() || reader.isCharacters() && reader.getText() == null) {
                    if (reader.isCharacters() && reader.getText() == null) { // hack: read null
                        result.add(null);
                        if (reader.hasNext()) {
                            reader.next();
                        } else {
                            break;
                        }
                    } else {
                        result.add(unmarshal(type, config, unmarshaller, reader));
                        if (documentArray && reader.hasNext()) { // move to next document
                            reader.next();
                        }
                    }
                }
            }
            reader.close();
            return result;
        }

        public void writeArray(Class<?> type, JsonXML config, JAXBContext context, Writer stream, Collection<?> collection)
                throws XMLStreamException, JAXBException {
            checkBindable(type);
            XMLStreamWriter writer = createXMLStreamWriter(type, config, stream);
            if (collection == null) { // hack: write null
                writer.writeCharacters(null);
            } else {
                Marshaller marshaller = context.createMarshaller();
                if (!writeDocumentArray) {
                    writer.writeStartDocument();
                    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
                }
                writer.writeProcessingInstruction(JsonXMLStreamConstants.MULTIPLE_PI_TARGET);
                for (Object value : collection) {
                    if (value == null) { // hack: write null
                        writer.writeCharacters(null);
                    } else {
                        marshal(type, config, marshaller, writer, value);
                    }
                }
                if (!writeDocumentArray) {
                    writer.writeEndDocument();
                }
            }
            writer.close();
        }
    }