/******************************************************************************* * 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<Class<?>, JAXBContext> jaxbDefaultContexts = new SoftConcurrentMap<Class<?>, JAXBContext>(); @Context protected Providers providers; private static final SoftConcurrentMap<Class<?>, Boolean> jaxbIsXMLRootElementCache = new SoftConcurrentMap<Class<?>, Boolean>(); private static final SoftConcurrentMap<Class<?>, Boolean> jaxbIsXMLTypeCache = new SoftConcurrentMap<Class<?>, Boolean>(); private static final SoftConcurrentMap<Class<?>, Class<?>> xmlElementConcreteClassCache = new SoftConcurrentMap<Class<?>, 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<?>, Class<?>> jaxbTypeMapCache = new SoftConcurrentMap<Class<?>, Class<?>>(); private static final SoftConcurrentMap<Type, XmlJavaTypeAdapter> xmlJavaTypeAdapterCache = new SoftConcurrentMap<Type, XmlJavaTypeAdapter>(); private static final SoftConcurrentMap<Type, Boolean> xmlJavaTypeAdapterPresentCache = new SoftConcurrentMap<Type, Boolean>(); // 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<JAXBContext, Marshaller> mpool = new Pool<JAXBContext, Marshaller>(); private Pool<JAXBContext, Unmarshaller> upool = new Pool<JAXBContext, Unmarshaller>(); // 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> xmlInputFactory = new ThreadLocal<XMLInputFactory>(); /** * 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<JAXBContext> _resolver; protected Type _type; private int hashCode = -1; public JAXBContextResolverKey(ContextResolver<JAXBContext> 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<JAXBContextResolverKey, JAXBContext> jaxbContextCache = new SoftConcurrentMap<JAXBContextResolverKey, JAXBContext>(); // 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<JAXBUnmarshalOptions> 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<Unmarshaller>() { 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<Marshaller>() { 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<XmlFormattingOptions> 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<JAXBContext> 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<JAXBContext>() { 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<Class<?>>() { 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 <K> Key * @param <V> Pooled object */ private static class Pool<K, V> { private SoftReference<ConcurrentHashMap<K, ArrayList<V>>> softMap = new SoftReference<ConcurrentHashMap<K, ArrayList<V>>>( new ConcurrentHashMap<K, ArrayList<V>>()); /** * 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<V> 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<V> 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<V> getValues(K key) { ConcurrentHashMap<K, ArrayList<V>> map = softMap.get(); ArrayList<V> 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<K, ArrayList<V>>(); softMap = new SoftReference<ConcurrentHashMap<K, ArrayList<V>>>(map); } values = new ArrayList<V>(); 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<K, ArrayList<V>> 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; } } } } }