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

org.apache.wink.common.internal.providers.entity.xml.AbstractJAXBProvider Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.wink.common.internal.providers.entity.xml;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Providers;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.wink.common.RuntimeContext;
import org.apache.wink.common.internal.WinkConfiguration;
import org.apache.wink.common.internal.i18n.Messages;
import org.apache.wink.common.internal.runtime.RuntimeContextTLS;
import org.apache.wink.common.internal.utils.JAXBUtils;
import org.apache.wink.common.internal.utils.MediaTypeUtils;
import org.apache.wink.common.internal.utils.SoftConcurrentMap;
import org.apache.wink.common.model.JAXBUnmarshalOptions;
import org.apache.wink.common.model.XmlFormattingOptions;
import org.apache.wink.common.utils.ProviderUtils;
import org.apache.wink.common.utils.ProviderUtils.PROVIDER_EXCEPTION_ORIGINATOR;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class AbstractJAXBProvider {

    protected static final Logger                                    logger                         =
                                                                                                        LoggerFactory
                                                                                                            .getLogger(AbstractJAXBProvider.class);
    private static final SoftConcurrentMap, JAXBContext>    jaxbDefaultContexts            =
                                                                                                        new SoftConcurrentMap, JAXBContext>();

    @Context
    protected Providers                                              providers;

    private static final SoftConcurrentMap, Boolean>        jaxbIsXMLRootElementCache      =
                                                                                                        new SoftConcurrentMap, Boolean>();

    private static final SoftConcurrentMap, Boolean>        jaxbIsXMLTypeCache             =
                                                                                                        new SoftConcurrentMap, Boolean>();

    private static final SoftConcurrentMap, Class>       xmlElementConcreteClassCache   =
                                                                                                        new SoftConcurrentMap, Class>();

    // if JAXB objects implement an interface where that interface has
    // @XmlJavaTypeAdapter annotation, or
    // in JAXB 2.2 if the @XMLElement annotation is on the 'type' of the
    // resource method parameter
    protected static final SoftConcurrentMap, Class>     jaxbTypeMapCache               =
                                                                                                        new SoftConcurrentMap, Class>();

    private static final SoftConcurrentMap xmlJavaTypeAdapterCache        =
                                                                                                        new SoftConcurrentMap();

    private static final SoftConcurrentMap            xmlJavaTypeAdapterPresentCache =
                                                                                                        new SoftConcurrentMap();

    // the Pool code for the pooling of unmarshallers is from Axis2 Java
    // http://svn.apache.org/repos/asf/webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/message/databinding/JAXBUtils.java
    //
    // These pools should *not* be static in Wink, however, because the
    // (un)marshallers are unique per JAXBContext instance, each
    // of which is unique per class object being (un)marshalled. In Axis2, the
    // JAXBContext instances cover the entire application space, thus
    // it was safe to cache them in a static field.
    private Pool                            mpool                          =
                                                                                                        new Pool();
    private Pool                          upool                          =
                                                                                                        new Pool();

    // For performance, it might seem advantageous to use a static
    // XMLInputFactory instance. However, this was shown to
    // be problematic on the Sun StAX parser (which is a fork of Apache Xerces)
    // under load stress test. So we use ThreadLocal.
    private static ThreadLocal                      xmlInputFactory                =
                                                                                                        new ThreadLocal();

    /**
     * This class is the key to the JAXBContext cache. It must be based on the
     * ContextResolver instance who created the JAXBContext and the type passed
     * to it. The only way this cache becomes invalid is if the ContextResolver
     * does something crazy like create JAXBContexts based on time -- it only
     * creates contexts between noon and 5:00pm. So, uhhh, don't do that.
     */
    private static class JAXBContextResolverKey {
        private static final Logger            logger   =
                                                            LoggerFactory
                                                                .getLogger(JAXBContextResolverKey.class);

        protected ContextResolver _resolver;
        protected Type                         _type;
        private int                            hashCode = -1;

        public JAXBContextResolverKey(ContextResolver resolver, Type type) {
            logger.trace("Constructing JAXBContextResolverKey with {} and {}", resolver, type); //$NON-NLS-1$
            // resolver may be null, which is ok; we'll protect against NPEs in
            // equals and hashCode overrides
            _resolver = resolver;
            _type = type;
        }

        @Override
        public boolean equals(Object o) {
            logger.trace("equals({}) entry", o); //$NON-NLS-1$
            if ((o == null) || (!(o instanceof JAXBContextResolverKey))) {
                logger.trace("equals() exit due to null or not instance of JAXBContextResolverKey"); //$NON-NLS-1$
                return false;
            }
            JAXBContextResolverKey obj = (JAXBContextResolverKey)o;
            // check for both null or both NOT null
            boolean result =
                ((obj._resolver == null) && (_resolver == null)) || ((obj._resolver != null) && (_resolver != null));
            logger.trace("null check result is {}", result); //$NON-NLS-1$
            // we can use hashCode() to compare _resolver
            boolean finalResult =
                result && (obj.hashCode() == hashCode()) && (obj._type.equals(_type));
            logger.trace("final result is {}", finalResult); //$NON-NLS-1$
            return finalResult;
        }

        @Override
        public int hashCode() {
            logger.trace("hashCode() entry"); //$NON-NLS-1$
            if (hashCode != -1) {
                logger.trace("returning hashCode {}", hashCode); //$NON-NLS-1$
                return hashCode;
            }
            if (_resolver == null) {
                // allow the key to be based entirely on the _type object
                // equality from equals method. Only YOU can prevent NPEs.
                hashCode = 0; // don't use _type's hashCode, as the instances
                // may differ between JAXBContextResolverKey
                // instances
                logger.trace("resolver is null so returning hashCode {}", hashCode); //$NON-NLS-1$
                return hashCode;
            }
            // Resolver instances may be unique due to the way we proxy the call
            // to get the instances in the ProvidersRegistry.
            // Therefore, we'll get better performance if we calculate the
            // hashCode from the package.classname of the ContextResolver.
            // However, this means we need to make sure the map that uses this
            // key is non-static, so it remains scoped at the
            // transaction level, rather than at the application, or worse, JVM
            // level.
            String resolverName = _resolver.getClass().getName();
            logger.trace("resolverName is {}", resolverName); //$NON-NLS-1$
            byte[] bytes = resolverName.getBytes();
            for (int i = 0; i < bytes.length; i++) {
                hashCode += bytes[i];
            }
            logger.trace("returning hashCode to be {}", hashCode); //$NON-NLS-1$
            return hashCode;
        }

    }

    /*
     * TODO: in my small, uncontrolled test, the JVM (garbage collector?) was
     * cleaning about 10% of the time. It may be worth considering the use of
     * LRU cache or something more directly under our control to gain more of
     * that. To observe this behavior, set the "loop" int in
     * JAXBCustomContextResolverCacheTest.testCustomResolverCacheOn to a high
     * number, and see the System.out for cacheMisses. In my checking, it was
     * about 10% of "loop".
     */
    // do not make static, as the key is based on the classname of the
    // ContextResolver
    private final SoftConcurrentMap jaxbContextCache =
                                                                                              new SoftConcurrentMap();

    // JAXBContext cache can be turned off through system property
    static private final String                                          propVal          =
                                                                                              System
                                                                                                  .getProperty("org.apache.wink.jaxbcontextcache");        //$NON-NLS-1$
    // non-final, protected only to make it unittestable
    static protected boolean                                             contextCacheOn   =
                                                                                              !((propVal != null) && (propVal
                                                                                                  .equalsIgnoreCase("off")));                              //$NON-NLS-1$

/**
     * Get the unmarshaller. You must call {@link #releaseJAXBUnmarshaller(JAXBContext, Unmarshaller) to put it back
     * into the pool.
     * 
     * @param context the current context
     * @return Unmarshaller an unmarshaller for the context
     * @throws JAXBException
     */
    protected Unmarshaller getJAXBUnmarshaller(Class type, JAXBContext context, MediaType mediaType)
        throws JAXBException {
        Unmarshaller unm = upool.get(context);
        if (unm == null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Unmarshaller created [not in pool]"); //$NON-NLS-1$
            }
            unm = internalCreateUnmarshaller(context);
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace("Unmarshaller obtained [from  pool]"); //$NON-NLS-1$
            }
        }

        if (providers != null) {
            ContextResolver contextResolver =
                providers.getContextResolver(JAXBUnmarshalOptions.class, mediaType);
            JAXBUnmarshalOptions options = null;
            if (contextResolver != null) {
                options = contextResolver.getContext(type);
            }
            if (options != null) {
                JAXBUtils.setJAXBUnmarshalOptions(unm, options);
            }
        }
        return unm;
    }

    /**
     * skips START_DOCUMENT, COMMENTs, PIs, and checks for DTD
     * 
     * @param reader
     * @throws XMLStreamException
     */
    private static void checkForDTD(XMLStreamReader reader) throws XMLStreamException {
        boolean supportDTD = false;

        int event = reader.getEventType();
        if (event != XMLStreamReader.START_DOCUMENT) {
            // something went horribly wrong; the reader passed into us has
            // already been partially processed
            throw new XMLStreamException(Messages.getMessage("badXMLReaderInitialStart")); //$NON-NLS-1$
        }
        while (event != XMLStreamReader.START_ELEMENT) { // all StAX parsers
                                                         // require a
                                                         // START_ELEMENT. See
                                                         // AbstractJAXBProviderTest
                                                         // class
            event = reader.next();
            if (event == XMLStreamReader.DTD) {

                RuntimeContext runtimeContext = RuntimeContextTLS.getRuntimeContext();
                WinkConfiguration winkConfig = runtimeContext.getAttribute(WinkConfiguration.class);
                if (winkConfig != null) {
                    Properties props = winkConfig.getProperties();
                    if (props != null) {
                        // use valueOf method to require the word "true"
                        supportDTD =
                            Boolean.valueOf(props.getProperty("wink.supportDTDEntityExpansion")); //$NON-NLS-1$
                    }
                }
                if (!supportDTD) {
                    throw new EntityReferenceXMLStreamException(Messages
                        .getMessage("entityRefsNotSupported")); //$NON-NLS-1$
                } else {
                    logger
                        .trace("DTD entity reference expansion is enabled.  This may present a security risk."); //$NON-NLS-1$
                }
            }
        }
    }

    private static XMLInputFactory getXMLInputFactory() {
        XMLInputFactory factory = xmlInputFactory.get();
        if (factory == null) {
            factory = XMLInputFactory.newInstance();
            xmlInputFactory.set(factory);
        }
        return factory;
    }

    /**
     * A consistent place to get a properly configured XMLStreamReader.
     * 
     * @param entityStream
     * @return
     * @throws XMLStreamException
     */
    protected XMLStreamReader getXMLStreamReader(InputStream entityStream)
        throws XMLStreamException {
        // NOTE: createFilteredReader may appear to be more convenient, but it
        // comes at the cost of
        // performance. This solution (to use checkForDTD) appears to be the
        // best solution to preserve
        // performance, but still achieve what we need to do.
        XMLStreamReader reader = getXMLInputFactory().createXMLStreamReader(entityStream);
        checkForDTD(reader);
        return reader;
    }

    /**
     * A consistent place to get a properly configured XMLStreamReader.
     * 
     * @param entityStream
     * @return
     * @throws XMLStreamException
     */
    protected XMLStreamReader getXMLStreamReader(InputStreamReader entityStreamReader)
        throws XMLStreamException {
        // NOTE: createFilteredReader may appear to be more convenient, but it
        // comes at the cost of
        // performance. This solution (to use checkForDTD) appears to be the
        // best solution to preserve
        // performance, but still achieve what we need to do.
        XMLStreamReader reader = getXMLInputFactory().createXMLStreamReader(entityStreamReader);
        checkForDTD(reader);
        return reader;
    }

    protected static void closeXMLStreamReader(XMLStreamReader xmlStreamReader) {
        if (xmlStreamReader != null) {
            try {
                xmlStreamReader.close();
            } catch (XMLStreamException e) {
                logger.trace("XMLStreamReader already closed.", e); //$NON-NLS-1$
            } catch (RuntimeException e) {
                logger.trace("RuntimeException occurred: ", e); //$NON-NLS-1$
            }
        }
    }

    private static Unmarshaller internalCreateUnmarshaller(final JAXBContext context)
        throws JAXBException {
        Unmarshaller unm;
        try {
            unm = AccessController.doPrivileged(new PrivilegedExceptionAction() {
                public Unmarshaller run() throws JAXBException {
                    return context.createUnmarshaller();
                }
            });
        } catch (PrivilegedActionException e) {
            throw (JAXBException)e.getCause();
        }
        return unm;
    }

    /**
     * Release Unmarshaller. Do not call this method if an exception occurred
     * while using the Unmarshaller. The object may be in an invalid state.
     * 
     * @param context JAXBContext the context to key off from
     * @param unmarshaller the unmarshaller to put back in the pool
     */
    protected void releaseJAXBUnmarshaller(JAXBContext context, Unmarshaller unmarshaller) {
        if (logger.isTraceEnabled()) {
            logger.trace("Unmarshaller placed back into pool"); //$NON-NLS-1$
        }
        unmarshaller.setAttachmentUnmarshaller(null);
        upool.put(context, unmarshaller);
    }

    private static Marshaller internalCreateMarshaller(final JAXBContext context)
        throws JAXBException {
        Marshaller marshaller;
        try {
            marshaller = AccessController.doPrivileged(new PrivilegedExceptionAction() {
                public Marshaller run() throws JAXBException {
                    return context.createMarshaller();
                }
            });
        } catch (PrivilegedActionException e) {
            throw (JAXBException)e.getCause();
        }
        return marshaller;
    }

    /**
     * Get JAXBMarshaller
     * 
     * @param context JAXBContext
     * @return Marshaller
     * @throws JAXBException
     */
    protected Marshaller getJAXBMarshaller(Class type, JAXBContext context, MediaType mediaType)
        throws JAXBException {

        Marshaller m = mpool.get(context);

        if (m == null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Marshaller created [not in pool]"); //$NON-NLS-1$
            }
            m = internalCreateMarshaller(context);
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace("Marshaller obtained [from  pool]"); //$NON-NLS-1$
            }
        }

        // will set to UTF-8 if there isn't a charset
        m.setProperty(Marshaller.JAXB_ENCODING, ProviderUtils.getCharset(mediaType));

        ContextResolver contextResolver =
            providers.getContextResolver(XmlFormattingOptions.class, mediaType);
        XmlFormattingOptions formatingOptions = null;
        if (contextResolver != null) {
            formatingOptions = contextResolver.getContext(type);
        }
        if (formatingOptions != null) {
            JAXBUtils.setXmlFormattingOptions(m, formatingOptions);
        }
        return m;
    }

    /**
     * Do not call this method if an exception occurred while using the
     * Marshaller. The object may be in an invalid state.
     * 
     * @param context JAXBContext
     * @param marshaller Marshaller
     */
    protected void releaseJAXBMarshaller(JAXBContext context, Marshaller marshaller) {
        if (logger.isTraceEnabled()) {
            logger.trace("Marshaller placed back into pool"); //$NON-NLS-1$
        }

        marshaller.setAttachmentMarshaller(null);
        mpool.put(context, marshaller);
    }

    protected boolean isSupportedMediaType(MediaType mediaType) {
        return MediaTypeUtils.isXmlType(mediaType);
    }

    public static boolean isJAXBObject(Class type, Type genericType) {
        if (isJAXBObject(type)) {
            return true;
        } else if (genericType instanceof Class) {
            return isJAXBObject((Class)genericType);
        }
        return false;
    }

    /**
     * Checks to see if type is marshallable. One of two annotations must be
     * present with the following conditions: 1) @XmlJavaTypeAdapter(type,
     * SomeotherType), or 2) @XmlElement(type=SomeotherType.class) where
     * SomeotherType is a JAXB object.
     * 
     * @param type
     * @param annotations
     * @return
     */
    public boolean isCompatible(Class type, Annotation[] annotations) {
        return isJAXBObject(getConcreteTypeFromTypeMap(type, annotations));
    }

    private Class getConcreteTypeFromAdapter(Class type, Annotation[] annotations) {
        XmlJavaTypeAdapter adapter = getXmlJavaTypeAdapter(type, type, annotations);
        if (adapter != null) {
            Class adapterClass = adapter.value();
            try {
                return (Class)adapterClass.getMethod("marshal", type).getReturnType();
            } catch (NoSuchMethodException e) {
                // not possible to get here;
                // compiler would have prevented compilation of an application
                // with an XmlJavaTypeAdapter that lacked a "marshal" method
            }
        }
        return type;
    }

    private Class getConcreteTypeFromXmlElementAnno(Class type, Annotation[] annotations) {
        Class ret = xmlElementConcreteClassCache.get(type);
        if (ret == null) {
            XmlElement xmlElement = getXmlElementAnno(type, annotations);
            if (xmlElement != null) {
                Class xmlElementType = xmlElement.type();
                if (xmlElementType != null) {
                    ret = xmlElementType;
                }
            }
            if (ret == null)
                ret = type;
            xmlElementConcreteClassCache.put(type, ret);
        }
        return ret;
    }

    public Class getConcreteTypeFromTypeMap(Class type, Annotation[] annotations) {
        Class concreteType = jaxbTypeMapCache.get(type);
        if (concreteType == null) {
            concreteType = getConcreteTypeFromAdapter(type, annotations);
            // @XmlJavaTypeAdapter takes priority over XmlElement
            if (concreteType == type) {
                concreteType = getConcreteTypeFromXmlElementAnno(type, annotations);
            }
            jaxbTypeMapCache.put(type, concreteType);
        }
        return concreteType;
    }

    @SuppressWarnings("unchecked")
    protected Object marshalWithXmlAdapter(Object obj, Class cls, Type type, Annotation[] annotations) {
        if ((type == null) || (annotations == null)) {
            return obj;
        }
        XmlJavaTypeAdapter xmlJavaTypeAdapter = getXmlJavaTypeAdapter(cls, type, annotations);
        if (xmlJavaTypeAdapter != null) {
            try {
                XmlAdapter xmlAdapter = xmlJavaTypeAdapter.value().newInstance();
                return xmlAdapter.marshal(obj);
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not marshal {} using {} due to exception:", new Object[] {
                        obj, xmlJavaTypeAdapter.value().getName(), e});
                }
            }
        }
        return obj;
    }

    /**
     * @param type
     * @param annotations
     * @return
     */
    private XmlJavaTypeAdapter getXmlJavaTypeAdapter(Class cls, Type type, Annotation[] annotations) {
        Boolean present = xmlJavaTypeAdapterPresentCache.get(type);
        if (Boolean.FALSE.equals(present)) {
            return null;
        }
        XmlJavaTypeAdapter xmlJavaTypeAdapter = xmlJavaTypeAdapterCache.get(type);
        if(xmlJavaTypeAdapter == null) {
            xmlJavaTypeAdapter = findXmlJavaTypeAdapter(cls, type, annotations);
            xmlJavaTypeAdapterCache.put(type, xmlJavaTypeAdapter);
            xmlJavaTypeAdapterPresentCache.put(type, xmlJavaTypeAdapter != null);
        }
        return xmlJavaTypeAdapter;
    }

    private XmlJavaTypeAdapter findXmlJavaTypeAdapter(Class cls, Type type, Annotation[] annotations) {
        XmlJavaTypeAdapter xmlJavaTypeAdapter = null;
        for (int i = 0; (annotations != null) && i < annotations.length; i++) {
            if (annotations[i].annotationType() == XmlJavaTypeAdapter.class) {
                xmlJavaTypeAdapter = (XmlJavaTypeAdapter)annotations[i];
                break;
            }
        }
        if ((xmlJavaTypeAdapter == null) && (cls != null)) {
            // check the type itself
            xmlJavaTypeAdapter = cls.getAnnotation(XmlJavaTypeAdapter.class);
        }
        return xmlJavaTypeAdapter;
    }

    /**
     * @param type
     * @param annotations
     * @return
     */
    private XmlElement getXmlElementAnno(Type type, Annotation[] annotations) {
        XmlElement xmlElement = null;
        for (int i = 0; (annotations != null) && i < annotations.length; i++) {
            if (annotations[i].annotationType() == XmlElement.class) {
                xmlElement = (XmlElement)annotations[i];
                break;
            }
        }
        return xmlElement;
    }

    @SuppressWarnings("unchecked")
    protected Object unmarshalWithXmlAdapter(Object obj, Class cls, Type type, Annotation[] annotations) {
        if ((type == null) || (annotations == null)) {
            return obj;
        }
        XmlJavaTypeAdapter xmlJavaTypeAdapter = getXmlJavaTypeAdapter(cls, type, annotations);
        if (xmlJavaTypeAdapter != null) {
            try {
                XmlAdapter xmlAdapter = xmlJavaTypeAdapter.value().newInstance();
                return xmlAdapter.unmarshal(obj);
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not unmarshal {} using {} due to exception:", new Object[] {
                        obj, xmlJavaTypeAdapter.value().getName(), e});
                }
            }
        }
        return obj;
    }

    public static boolean isJAXBObject(Class type) {
        return isXMLRootElement(type) || isXMLType(type);
    }

    private static boolean isXMLRootElement(Class type) {
        Boolean isJAXBObject = jaxbIsXMLRootElementCache.get(type);

        if (isJAXBObject == null) {
            boolean isXmlRootElement = type.getAnnotation(XmlRootElement.class) != null;
            isJAXBObject = Boolean.valueOf(isXmlRootElement);
            jaxbIsXMLRootElementCache.put(type, isJAXBObject);
        }

        return isJAXBObject.booleanValue();
    }

    private static boolean isXMLType(Class type) {
        Boolean isJAXBObject = jaxbIsXMLTypeCache.get(type);

        if (isJAXBObject == null) {
            boolean isXmlTypeElement = type.getAnnotation(XmlType.class) != null;
            isJAXBObject = Boolean.valueOf(isXmlTypeElement);
            jaxbIsXMLTypeCache.put(type, isJAXBObject);
        }

        return isJAXBObject.booleanValue();
    }

    public static boolean isJAXBElement(Class type, Type genericType) {
        return (type == JAXBElement.class);
    }

    protected JAXBContext getContext(Class type, MediaType mediaType) throws JAXBException {
        return getContext(type, type, mediaType);
    }

    protected JAXBContext getContext(Class type, Type genericType, MediaType mediaType)
        throws JAXBException {
        if (logger.isTraceEnabled()) {
            logger
                .trace("getContext({}, {}, {}) entry", new Object[] {type, genericType, mediaType}); //$NON-NLS-1$
        }
        ContextResolver contextResolver =
            providers.getContextResolver(JAXBContext.class, mediaType);

        JAXBContext context = null;

        JAXBContextResolverKey key = null;
        if (contextCacheOn) {
            logger.trace("contextCacheOn is true"); //$NON-NLS-1$
            // it's ok and safe for contextResolver to be null at this point.
            // JAXBContextResolverKey can handle it
            key = new JAXBContextResolverKey(contextResolver, type);
            if (logger.isTraceEnabled()) {
                logger
                    .trace("created JAXBContextResolverKey {} for ({}, {}, {})", new Object[] {key, type, genericType, mediaType}); //$NON-NLS-1$
            }
            context = jaxbContextCache.get(key);
            logger.trace("retrieved context {}", context); //$NON-NLS-1$
            if (context != null) {
                if (logger.isTraceEnabled()) {
                    logger
                        .trace("retrieved context {}@{}", context.getClass().getName(), System.identityHashCode(context)); //$NON-NLS-1$
                    logger.trace("returned context {}", context); //$NON-NLS-1$
                }
                return context;
            }
        }

        if (contextResolver != null) {
            try {
                context = contextResolver.getContext(type);
            } catch (RuntimeException e) {
                ProviderUtils.logUserProviderException(e,
                                                       contextResolver,
                                                       PROVIDER_EXCEPTION_ORIGINATOR.getContext,
                                                       new Object[] {type},
                                                       RuntimeContextTLS.getRuntimeContext());
            }
        }

        if (context == null) {
            context = getDefaultContext(type, genericType);
        }

        if (contextCacheOn) {
            logger.trace("put key {} and context {} into jaxbContextCache", key, context); //$NON-NLS-1$
            jaxbContextCache.put(key, context);
        }

        if (logger.isTraceEnabled()) {
            logger.trace("returned context {}", context); //$NON-NLS-1$
            logger
                .trace("retrieved context {}@{}", context.getClass().getName(), System.identityHashCode(context)); //$NON-NLS-1$
        }
        return context;
    }

    private JAXBContext getDefaultContext(final Class type, final Type genericType)
        throws JAXBException {
        logger.trace("getDefaultContext({}, {}) entry", type, genericType); //$NON-NLS-1$
        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction() {

                public JAXBContext run() throws Exception {
                    JAXBContext context = jaxbDefaultContexts.get(type);
                    if (context == null) {

                        // CAUTION: be careful with this. Adding a second or
                        // more classes to
                        // the JAXBContext has the side
                        // effect of putting a namespace prefix and the
                        // namespace decl on
                        // the subelements of the
                        // desired type, thus degrading performance.

                        if (!isXMLRootElement(type) && !isXMLType(type)) { // use
                            // genericType.
                            // If that fails,
                            // we'll know
                            // soon enough
                            logger.trace("Using genericType to create context"); //$NON-NLS-1$
                            context = JAXBContext.newInstance((Class)genericType);
                        } else {
                            logger.trace("Using type to create context"); //$NON-NLS-1$
                            context = JAXBContext.newInstance(type);
                        }

                        jaxbDefaultContexts.put(type, context);
                    }
                    if (logger.isTraceEnabled()) {
                        logger.trace("getDefaultContext() exit returning", context); //$NON-NLS-1$
                        logger
                            .trace("returning context {}@{}", context.getClass().getName(), System.identityHashCode(context)); //$NON-NLS-1$
                    }
                    return context;
                }

            });
        } catch (PrivilegedActionException e) {
            throw (JAXBException)e.getException();
        }
    }

    /**
     * If the object is not a JAXBElement and is annotated with XmlType but not
     * with XmlRootElement, then it is automatically wrapped in a JAXBElement
     * 
     * @param t
     * @param type
     * @return
     */
    protected Object getEntityToMarshal(Object jaxbObject, Class type) {
        // in case JAXB Objects is not annotated with XmlRootElement, Wrap JAXB
        // Objects with JAXBElement
        if (!isXMLRootElement(type) && isXMLType(type)) {
            JAXBElement wrappedJAXBElement = wrapInJAXBElement(jaxbObject, type);
            if (wrappedJAXBElement == null) {
                if (logger.isErrorEnabled()) {
                    logger.error(Messages.getMessage("jaxbObjectFactoryNotFound", type.getName())); //$NON-NLS-1$
                }
                throw new WebApplicationException();
            }
            return wrappedJAXBElement;
        }
        return jaxbObject;
    }

    /**
     * If this object is managed by an XmlRegistry, this method will invoke the
     * registry and wrap the object in a JAXBElement so that it can be
     * marshalled.
     */
    private JAXBElement wrapInJAXBElement(Object jaxbObject, Class type) {
        try {
            Object factory = null;
            Class factoryClass = findDefaultObjectFactoryClass(type);
            if (factoryClass != null) {
                factory = factoryClass.newInstance();
                Method[] method = factory.getClass().getDeclaredMethods();
                for (int i = 0; i < method.length; i++) {
                    // Invoke method
                    Method current = method[i];
                    if (current.getParameterTypes().length == 1 && current.getParameterTypes()[0]
                        .equals(type)
                        && current.getName().startsWith("create")) { //$NON-NLS-1$
                        Object result = current.invoke(factory, new Object[] {jaxbObject});
                        return JAXBElement.class.cast(result);
                    }
                }
                return null;
            }
            if (logger.isWarnEnabled()) {
                logger.warn(Messages.getMessage("jaxbObjectFactoryInstantiate", type.getName())); //$NON-NLS-1$
            }
            return defaultWrapInJAXBElement(jaxbObject, type);
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error(Messages.getMessage("jaxbElementFailToBuild", type.getName()), e); //$NON-NLS-1$
            }
            return null;
        }

    }

    private Class findDefaultObjectFactoryClass(Class type) {
        // XmlType typeAnnotation = type.getAnnotation(XmlType.class);
        // // Check that class factory method uses
        // if (!typeAnnotation.factoryClass().equals(XmlType.DEFAULT.class)) {
        // logger.error("Failed to build JAXBElement for {}", type.getName());
        // return null;
        // }
        // Search for Factory
        final StringBuilder b = new StringBuilder(type.getPackage().getName());
        b.append(".ObjectFactory"); //$NON-NLS-1$
        Class factoryClass = null;
        try {
            factoryClass = AccessController.doPrivileged(new PrivilegedExceptionAction>() {
                public Class run() throws ClassNotFoundException {
                    return Thread.currentThread().getContextClassLoader().loadClass(b.toString());
                }
            });
        } catch(PrivilegedActionException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(Messages.getMessage("jaxbObjectFactoryNotFound", type.getName()), e); //$NON-NLS-1$
            }
            return null;
        }

        if (!factoryClass.isAnnotationPresent(XmlRegistry.class)) {
            if (logger.isErrorEnabled()) {
                logger.error(Messages.getMessage("jaxbObjectFactoryNotAnnotatedXMLRegistry", type //$NON-NLS-1$
                    .getName()));
            }
            return null;
        }

        return factoryClass;
    }

    @SuppressWarnings("unchecked")
    private JAXBElement defaultWrapInJAXBElement(Object jaxbObject, Class type) {
        if (logger.isInfoEnabled()) {
            logger.info(Messages.getMessage("jaxbCreateDefaultJAXBElement", type.getName())); //$NON-NLS-1$
        }
        String typeStr = type.getAnnotation(XmlType.class).name();
        return new JAXBElement(new QName(typeStr), type, jaxbObject);
    }

    /**
     * Pool a list of items for a specific key
     * 
     * @param  Key
     * @param  Pooled object
     */
    private static class Pool {
        private SoftReference>> softMap         =
                                                                                      new SoftReference>>(
                                                                                                                                            new ConcurrentHashMap>());
        /**
         * Maximum number of JAXBContexts to store
         */
        private static int                                        MAX_LOAD_FACTOR = 32;

        /** The maps are freed up when a LOAD FACTOR is hit */
        private static int                                        MAX_LIST_FACTOR = 50;

        /**
         * @param key
         * @return removed item from pool or null.
         */
        public V get(K key) {
            List values = getValues(key);
            synchronized (values) {
                if (values.size() > 0) {
                    V v = values.remove(values.size() - 1);
                    return v;
                }
            }
            return null;
        }

        /**
         * Add item back to pool
         * 
         * @param key
         * @param value
         */
        public void put(K key, V value) {
            adjustSize();
            List values = getValues(key);
            synchronized (values) {
                if (values.size() < MAX_LIST_FACTOR) {
                    values.add(value);
                }
            }
        }

        /**
         * Get or create a list of the values for the key
         * 
         * @param key
         * @return list of values (never null)
         */
        private List getValues(K key) {
            ConcurrentHashMap> map = softMap.get();
            ArrayList values = null;
            if (map != null) {
                values = map.get(key);
                if (values != null) {
                    return values;
                }
            }
            synchronized (this) {
                if (map != null) {
                    values = map.get(key);
                }
                if (values == null) {
                    if (map == null) {
                        map = new ConcurrentHashMap>();
                        softMap = new SoftReference>>(map);
                    }
                    values = new ArrayList();
                    map.put(key, values);
                }
                return values;
            }
        }

        /**
         * When the number of keys exceeds the maximum load, half of the entries
         * are deleted. The assumption is that the JAXBContexts, UnMarshallers,
         * Marshallers, etc. require a large footprint.
         */
        private void adjustSize() {
            ConcurrentHashMap> map = softMap.get();
            if (map != null && map.size() > MAX_LOAD_FACTOR) {
                // Remove every other Entry in the map.
                Iterator it = map.entrySet().iterator();
                boolean removeIt = false;
                while (it.hasNext()) {
                    it.next();
                    if (removeIt) {
                        it.remove();
                    }
                    removeIt = !removeIt;
                }
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy