# JsonXMLOutputFactory.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;

    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.UnsupportedEncodingException;
    import java.io.Writer;
    import java.util.Arrays;

    import javax.xml.namespace.QName;
    import javax.xml.stream.FactoryConfigurationError;
    import javax.xml.stream.XMLEventWriter;
    import javax.xml.stream.XMLStreamException;
    import javax.xml.stream.XMLStreamWriter;

    import org.apache.synapse.commons.staxon.core.base.AbstractXMLOutputFactory;
    import org.apache.synapse.commons.staxon.core.event.SimpleXMLEventWriter;
    import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamFactory;
    import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamTarget;
    import org.apache.synapse.commons.staxon.core.json.stream.util.RemoveRootTarget;
    import org.apache.synapse.commons.staxon.core.json.stream.util.CustomRegexMatchReplaceIgnoreAutoPrimitiveTarget;
    import org.apache.synapse.commons.staxon.core.json.stream.util.CustomRegexIgnoreAutoPrimitiveTarget;
    import org.apache.synapse.commons.staxon.core.json.stream.util.AutoArrayTarget;
    import org.apache.synapse.commons.staxon.core.json.stream.util.AutoPrimitiveTarget;

    /**
     * XML output factory for streaming to JSON.
     */
    public class JsonXMLOutputFactory extends AbstractXMLOutputFactory {
        /**
         * <p>Start/end arrays automatically?</p>
         * <p/>
         * <p>The default value is <code>false</code>.</p>
         */
        public static final String PROP_AUTO_ARRAY = "JsonXMLOutputFactory.autoArray";

        /**
         * <p>Convert element text to JSON primitives (number, boolean, null) automatically?</p>
         * <p/>
         * <p>The default value is <code>false</code>.</p>
         */
        public static final String PROP_AUTO_PRIMITIVE = "JsonXMLOutputFactory.autoPrimitive";

        /**
         * <p>Whether to use the {@link JsonXMLStreamConstants#MULTIPLE_PI_TARGET}
         * processing instruction target to trigger an array start.
         * If <code>true</code>, a PI is used to inform the writer to begin an array,
         * passing the name of following multiple elements as data.
         * The writer will close arrays automatically.</p>
         * <p/>
         * <p>Note that the element given in the PI may be written zero times,
         * indicating an empty array.</p>
         * <p/>
         * <p>The default value is true.</p>
         */
        public static final String PROP_MULTIPLE_PI = "JsonXMLOutputFactory.multiplePI";

        /**
         * <p>JSON documents may have have multiple root properties. However,
         * XML requires a single root element. This property takes the name
         * of a "virtual" root element, which will be removed from the stream
         * when writing.</p>
         * <p/>
         * <p>The default value is <code>null</code>.</p>
         */
        public static final String PROP_VIRTUAL_ROOT = "JsonXMLOutputFactory.virtualRoot";

        /**
         * <p>Namespace prefix separator.</p>
         * <p/>
         * <p>The default value is <code>':'</code>.</p>
         */
        public static final String PROP_NAMESPACE_SEPARATOR = "JsonXMLOutputFactory.namespaceSeparator";

        /**
         * <p>Whether to write namespace declarations.</p>
         * <p/>
         * <p>The default value is <code>true</code>.</p>
         */
        public static final String PROP_NAMESPACE_DECLARATIONS = "JsonXMLOutputFactory.namespaceDeclarations";

        /**
         * <p>Format output for better readability?</p>
         * <p/>
         * <p>The default value is <code>false</code>.</p>
         */
        public static final String PROP_PRETTY_PRINT = "JsonXMLOutputFactory.prettyPrint";

        /**
         * <p>Convert element text to JSON primitives with ignore given regex (number, boolean, null) automatically?</p>
         *
         * <p>The default value is <code>null</code>.</p>
         */
        public static final String PROP_CUSTOM_REGEX = "JsonXMLOutputFactory.customRegex";

        public static final String PROP_CUSTOM_REPLACE_REGEX = "JsonXMLOutputFactory.customReplaceRegex";

        public static final String PROP_CUSTOM_REPLACE_SEQUENCE = "JsonXMLOutputFactory.customReplaceSequence";

        private JsonStreamFactory streamFactory;
        private boolean multiplePI;
        private QName virtualRoot;
        private boolean autoArray;
        private boolean autoPrimitive;
        private boolean prettyPrint;
        private char namespaceSeparator;
        private boolean namespaceDeclarations;
        private String customRegex;
        private String customReplaceRegex;
        private String customReplaceSequence;
        private boolean xmlNilReadWriteEnabled;
        private boolean xmlWriteNullForEmptyElement;
        private boolean preserverNamespacesForJson;
        private boolean processNCNames;

        private JsonXMLConfig config;

        public JsonXMLOutputFactory() throws FactoryConfigurationError {
            this(JsonXMLConfig.DEFAULT);
        }

        public JsonXMLOutputFactory(JsonStreamFactory streamFactory) {
            this(JsonXMLConfig.DEFAULT, streamFactory);
        }

        public JsonXMLOutputFactory(JsonXMLConfig config) throws FactoryConfigurationError {
            this(config, JsonStreamFactory.newFactory());
        }

        public JsonXMLOutputFactory(JsonXMLConfig config, JsonStreamFactory streamFactory) {
            this.config = config;
            this.multiplePI = config.isMultiplePI();
            this.virtualRoot = config.getVirtualRoot();
            this.autoArray = config.isAutoArray();
            this.autoPrimitive = config.isAutoPrimitive();
            this.prettyPrint = config.isPrettyPrint();
            this.namespaceSeparator = config.getNamespaceSeparator();
            this.namespaceDeclarations = config.isNamespaceDeclarations();
            this.streamFactory = streamFactory;
            this.customRegex= config.getCustomRegex();
            this.customReplaceRegex = config.getCustomReplaceRegex();
            this.customReplaceSequence = config.getCustomReplaceSequence();
            this.xmlNilReadWriteEnabled = config.isReadWriteXmlNil();
            this.xmlWriteNullForEmptyElement = config.isWriteNullForEmptyElements();
            this.preserverNamespacesForJson = config.isPreserverNamespacesForJson();
            this.processNCNames = config.isProcessNCNames();

    		/*
             * initialize standard properties
    		 */
            super.setProperty(IS_REPAIRING_NAMESPACES, config.isRepairingNamespaces());
        }

        private JsonStreamTarget decorate(JsonStreamTarget target) {
            if (virtualRoot != null) {
                target = new RemoveRootTarget(target, virtualRoot, namespaceSeparator);
            }
            if (autoArray) {
                target = new AutoArrayTarget(target);
            }
            if (autoPrimitive) {
                if (customRegex != null) {
                    target = new CustomRegexIgnoreAutoPrimitiveTarget(target, false, customRegex);
                } else if (customReplaceRegex != null) {
                    target = new CustomRegexMatchReplaceIgnoreAutoPrimitiveTarget(target, false, customReplaceRegex, customReplaceSequence);
                } else {
                    target = new AutoPrimitiveTarget(target, false);
                }
            }
            return target;
        }

        @Override
        public JsonXMLStreamWriter createXMLStreamWriter(OutputStream stream, String encoding) throws XMLStreamException {
            try {
                return createXMLStreamWriter(new OutputStreamWriter(stream, encoding));
            } catch (UnsupportedEncodingException e) {
                throw new XMLStreamException(e);
            }
        }

        @Override
        public JsonXMLStreamWriter createXMLStreamWriter(Writer stream) throws XMLStreamException {
            boolean repairNamespaces = Boolean.TRUE.equals(getProperty(IS_REPAIRING_NAMESPACES));
            try {
                return new JsonXMLStreamWriter(decorate(streamFactory.createJsonStreamTarget(stream, prettyPrint)),
                        repairNamespaces, multiplePI, namespaceSeparator, namespaceDeclarations, xmlNilReadWriteEnabled,
                        xmlWriteNullForEmptyElement);
            } catch (IOException e) {
                throw new XMLStreamException(e);
            }
        }

        @Override
        public JsonXMLStreamWriter createXMLStreamWriter(OutputStream stream) throws XMLStreamException {
            boolean repairNamespaces = Boolean.TRUE.equals(getProperty(IS_REPAIRING_NAMESPACES));
            try {
                return new JsonXMLStreamWriter(decorate(streamFactory.createJsonStreamTarget(stream, prettyPrint)),
                        repairNamespaces, multiplePI, namespaceSeparator, namespaceDeclarations, xmlNilReadWriteEnabled,
                        xmlWriteNullForEmptyElement);
            } catch (IOException e) {
                throw new XMLStreamException(e);
            }
        }

        @Override
        public XMLEventWriter createXMLEventWriter(XMLStreamWriter writer) throws XMLStreamException {
            return new SimpleXMLEventWriter(writer);
        }

        @Override
        public boolean isPropertySupported(String name) {
            return super.isPropertySupported(name)
                    || Arrays.asList(PROP_AUTO_ARRAY, PROP_MULTIPLE_PI, PROP_VIRTUAL_ROOT, PROP_NAMESPACE_SEPARATOR, PROP_NAMESPACE_DECLARATIONS, PROP_PRETTY_PRINT, PROP_CUSTOM_REPLACE_REGEX, PROP_CUSTOM_REPLACE_SEQUENCE).contains(name);
        }

        @Override
        public Object getProperty(String name) throws IllegalArgumentException {
            if (super.isPropertySupported(name)) {
                return super.getProperty(name);
            } else { // proprietary properties
                if (PROP_AUTO_ARRAY.equals(name)) {
                    return Boolean.valueOf(autoArray);
                } else if (PROP_AUTO_PRIMITIVE.equals(name)) {
                    return Boolean.valueOf(autoPrimitive);
                } else if (PROP_MULTIPLE_PI.equals(name)) {
                    return Boolean.valueOf(multiplePI);
                } else if (PROP_VIRTUAL_ROOT.equals(name)) {
                    return virtualRoot;
                } else if (PROP_PRETTY_PRINT.equals(name)) {
                    return Boolean.valueOf(prettyPrint);
                } else if (PROP_NAMESPACE_SEPARATOR.equals(name)) {
                    return namespaceSeparator;
                } else if (PROP_NAMESPACE_DECLARATIONS.equals(name)) {
                    return Boolean.valueOf(namespaceDeclarations);
                } else if (PROP_CUSTOM_REGEX.equals(name)) {
                    return customRegex;
                } else if (PROP_CUSTOM_REPLACE_REGEX.equals(name)) {
                    return customReplaceRegex;
                } else if (PROP_CUSTOM_REPLACE_SEQUENCE.equals(name)) {
                    return customReplaceSequence;
                } else {
                    throw new IllegalArgumentException("Unsupported property: " + name);
                }
            }
        }

        @Override
        public void setProperty(String name, Object value) throws IllegalArgumentException {
            if (super.isPropertySupported(name)) {
                super.setProperty(name, value);
            } else { // proprietary properties
                if (PROP_AUTO_ARRAY.equals(name)) {
                    autoArray = ((Boolean) value).booleanValue();
                } else if (PROP_AUTO_PRIMITIVE.equals(name)) {
                    autoPrimitive = ((Boolean) value).booleanValue();
                } else if (PROP_MULTIPLE_PI.equals(name)) {
                    multiplePI = ((Boolean) value).booleanValue();
                } else if (PROP_VIRTUAL_ROOT.equals(name)) {
                    virtualRoot = value instanceof String ? QName.valueOf((String) value) : (QName) value;
                } else if (PROP_PRETTY_PRINT.equals(name)) {
                    prettyPrint = ((Boolean) value).booleanValue();
                } else if (PROP_NAMESPACE_SEPARATOR.equals(name)) {
                    namespaceSeparator = (Character) value;
                } else if (PROP_NAMESPACE_DECLARATIONS.equals(name)) {
                    namespaceDeclarations = ((Boolean) value).booleanValue();
                } else if (PROP_CUSTOM_REGEX.equals(name) && value instanceof String) {
                    customRegex = (String) value;
                } else if (PROP_CUSTOM_REPLACE_REGEX.equals(name) && value instanceof String) {
                    customReplaceRegex = (String) value;
                } else if (PROP_CUSTOM_REPLACE_SEQUENCE.equals(name) && value instanceof String) {
                    customReplaceSequence = (String) value;
                } else {
                    throw new IllegalArgumentException("Unsupported property: " + name);
                }
            }
        }

        public JsonXMLConfig getConfig() {
            return this.config;
        }
    }