# JsonXMLRootProvider.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.lang.reflect.Method;

    import javax.xml.XMLConstants;
    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.annotation.XmlElementDecl;
    import javax.xml.bind.annotation.XmlNs;
    import javax.xml.bind.annotation.XmlRegistry;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.XmlSchema;
    import javax.xml.bind.annotation.XmlType;
    import javax.xml.namespace.QName;

    /**
     * Root name/element provider.
     */
    public class JsonXMLRootProvider {
        protected String getNamespaceURI(XmlType xmlType, XmlSchema xmlSchema) {
            if ("##default".equals(xmlType.namespace())) {
                return xmlSchema == null ? XMLConstants.NULL_NS_URI : xmlSchema.namespace();
            } else {
                return xmlType.namespace();
            }
        }

        protected String getNamespaceURI(XmlRootElement xmlRootElement, XmlSchema xmlSchema) {
            if ("##default".equals(xmlRootElement.namespace())) {
                return xmlSchema == null ? XMLConstants.NULL_NS_URI : xmlSchema.namespace();
            } else {
                return xmlRootElement.namespace();
            }
        }

        protected String getNamespaceURI(XmlElementDecl xmlElementDecl, XmlSchema xmlSchema) {
            if ("##default".equals(xmlElementDecl.namespace())) {
                return xmlSchema == null ? XMLConstants.NULL_NS_URI : xmlSchema.namespace();
            } else {
                return xmlElementDecl.namespace();
            }
        }

        protected String getPrefix(String namespaceURI, XmlSchema xmlSchema) {
            if (xmlSchema != null) {
                for (XmlNs xmlns : xmlSchema.xmlns()) {
                    if (xmlns.namespaceURI().equals(namespaceURI)) {
                        return xmlns.prefix();
                    }
                }
            }
            return XMLConstants.DEFAULT_NS_PREFIX;
        }

        /**
         * Calculate root element name for an <code>@XmlRootElement</code>-annotated type.
         *
         * @param type
         * @return element name
         */
        protected QName getXmlRootElementName(Class<?> type) {
            XmlRootElement xmlRootElement = type.getAnnotation(XmlRootElement.class);
            if (xmlRootElement == null) {
                return null;
            }
            String localName;
            if ("##default".equals(xmlRootElement.name())) {
                localName = Character.toLowerCase(type.getSimpleName().charAt(0)) + type.getSimpleName().substring(1);
            } else {
                localName = xmlRootElement.name();
            }
            XmlSchema xmlSchema = type.getPackage().getAnnotation(XmlSchema.class);
            String namespaceURI = getNamespaceURI(xmlRootElement, xmlSchema);
            return new QName(namespaceURI, localName, getPrefix(namespaceURI, xmlSchema));
        }

        /**
         * Calculate root element name for an <code>@XmlType</code>-annotated type.
         *
         * @param type
         * @return element name
         */
        protected QName getXmlTypeName(Class<?> type) {
            Method method = getXmlElementDeclMethod(type);
            if (method == null) {
                return null;
            }
            XmlElementDecl xmlElementDecl = method.getAnnotation(XmlElementDecl.class);
            if (xmlElementDecl == null) {
                return null;
            }
            XmlSchema xmlSchema = type.getPackage().getAnnotation(XmlSchema.class);
            String namespaceURI = getNamespaceURI(xmlElementDecl, xmlSchema);
            return new QName(namespaceURI, xmlElementDecl.name(), getPrefix(namespaceURI, xmlSchema));
        }

        /**
         * Determine <code>@XmlElementDecl</code>-annotated factory method to create {@link JAXBElement}
         * for an <code>@XmlType</code>-annotated type
         *
         * @param type
         * @return element
         */
        protected Method getXmlElementDeclMethod(Class<?> type) {
            XmlType xmlType = type.getAnnotation(XmlType.class);
            if (xmlType == null) {
                return null;
            }
            Class<?> factoryClass = xmlType.factoryClass();
            if (factoryClass == XmlType.DEFAULT.class) {
                String defaultObjectFactoryName = type.getPackage().getName() + ".ObjectFactory";
                try {
                    factoryClass = Thread.currentThread().getContextClassLoader().loadClass(defaultObjectFactoryName);
                } catch (Exception e) {
                    factoryClass = type;
                }
            }
            if (factoryClass.getAnnotation(XmlRegistry.class) == null) {
                return null;
            }
            XmlSchema xmlSchema = type.getPackage().getAnnotation(XmlSchema.class);
            String namespaceURI = getNamespaceURI(xmlType, xmlSchema);
            for (Method method : factoryClass.getDeclaredMethods()) {
                XmlElementDecl xmlElementDecl = method.getAnnotation(XmlElementDecl.class);
                if (xmlElementDecl != null && namespaceURI.equals(getNamespaceURI(xmlElementDecl, xmlSchema))) {
                    if (method.getReturnType() == JAXBElement.class) {
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length == 1 && parameterTypes[0] == type) {
                            return method;
                        }
                    }
                }
            }
            return null;
        }

        /**
         * Calculate root element name for an
         * <code>@XmlRootElement</code> or <code>@XmlType</code>-annotaed type.
         *
         * @param type
         * @return name or <code>null</code>
         */
        public QName getName(Class<?> type) {
            if (type.getAnnotation(XmlRootElement.class) != null) {
                return getXmlRootElementName(type);
            } else if (type.getAnnotation(XmlType.class) != null) {
                return getXmlTypeName(type);
            } else {
                return null;
            }
        }

        /**
         * Create root element for an
         * <code>@XmlRootElement</code> or <code>@XmlType</code>-annotaed type.
         *
         * @param type
         * @param value
         * @return root element (or <code>null</code>)
         */
        public JAXBElement<?> createElement(Class<?> type, Object value) throws JAXBException {
            if (type.getAnnotation(XmlRootElement.class) != null) {
                QName name = getXmlRootElementName(type);
                if (name != null) {
                    @SuppressWarnings({"rawtypes", "unchecked"})
                    JAXBElement<?> genericElement = new JAXBElement(name, type, value);
                    return genericElement;
                }
            } else if (type.getAnnotation(XmlType.class) != null) {
                Method method = getXmlElementDeclMethod(type);
                if (method != null) {
                    try {
                        return (JAXBElement<?>) method.invoke(method.getDeclaringClass().newInstance(), value);
                    } catch (Exception e) {
                        throw new JAXBException("Cannot create JAXBElement", e);
                    }
                }
            }
            return null;
        }
    }