| /* |
| * 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.axis2.jaxws.message.databinding; |
| |
| import org.apache.axis2.java.security.AccessController; |
| import org.apache.axis2.jaxws.ExceptionFactory; |
| import org.apache.axis2.jaxws.i18n.Messages; |
| import org.apache.axis2.jaxws.message.factory.ClassFinderFactory; |
| import org.apache.axis2.jaxws.registry.FactoryRegistry; |
| import org.apache.axis2.jaxws.utility.ClassUtils; |
| import org.apache.axis2.jaxws.utility.JavaUtils; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import javax.xml.bind.JAXBContext; |
| import javax.xml.bind.JAXBException; |
| import javax.xml.bind.JAXBIntrospector; |
| import javax.xml.bind.Marshaller; |
| import javax.xml.bind.Unmarshaller; |
| import javax.xml.bind.annotation.XmlType; |
| import javax.xml.ws.Holder; |
| import javax.xml.ws.wsaddressing.W3CEndpointReference; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.lang.annotation.Annotation; |
| import java.lang.ref.SoftReference; |
| import java.lang.reflect.AnnotatedElement; |
| import java.net.URL; |
| import java.net.URLDecoder; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| |
| /** |
| * JAXB Utilites to pool JAXBContext and related objects. |
| */ |
| public class JAXBUtils { |
| |
| private static final Log log = LogFactory.getLog(JAXBUtils.class); |
| |
| // Create a concurrent map to get the JAXBObject: |
| // key is the String (sorted packages) |
| // value is a SoftReference to a ConcurrentHashMap of Classloader keys and JAXBContextValue objects |
| // It is a soft map to encourage GC in low memory situations |
| private static Map< |
| String, |
| SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>>> jaxbMap = |
| new ConcurrentHashMap<String, |
| SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>>>(); |
| |
| private static Pool<JAXBContext, Marshaller> mpool = new Pool<JAXBContext, Marshaller>(); |
| private static Pool<JAXBContext, Unmarshaller> upool = new Pool<JAXBContext, Unmarshaller>(); |
| private static Pool<JAXBContext, JAXBIntrospector> ipool = new Pool<JAXBContext, JAXBIntrospector>(); |
| |
| // From Lizet Ernand: |
| // If you really care about the performance, |
| // and/or your application is going to read a lot of small documents, |
| // then creating Unmarshaller could be relatively an expensive operation. |
| // In that case, consider pooling Unmarshaller objects. |
| // Different threads may reuse one Unmarshaller instance, |
| // as long as you don't use one instance from two threads at the same time. |
| // ENABLE_ADV_POOLING is false...which means they are obtained from the JAXBContext instead of |
| // from the pool. |
| private static boolean ENABLE_MARSHALL_POOLING = true; |
| private static boolean ENABLE_UNMARSHALL_POOLING = true; |
| private static boolean ENABLE_INTROSPECTION_POOLING = false; |
| |
| private static int MAX_LOAD_FACTOR = 32; // Maximum number of JAXBContext to store |
| |
| // Construction Type |
| public enum CONSTRUCTION_TYPE { |
| BY_CLASS_ARRAY, BY_CONTEXT_PATH, UNKNOWN} |
| |
| ; |
| |
| // Some packages have a known set of classes. |
| // This map is immutable after its static creation. |
| private static final Map<String, List<Class>> specialMap = new HashMap<String, List<Class>>(); |
| static { |
| // The javax.xml.ws.wsaddressing package has a single class (W3CEndpointReference) |
| List<Class> classes = new ArrayList<Class>(); |
| classes.add(W3CEndpointReference.class); |
| specialMap.put("javax.xml.ws.wsaddressing", classes); |
| } |
| |
| public static final String DEFAULT_NAMESPACE_REMAP = getDefaultNamespaceRemapProperty(); |
| |
| /** |
| * Get a JAXBContext for the class |
| * |
| * @param contextPackage Set<Package> |
| * @return JAXBContext |
| * @throws JAXBException |
| * @deprecated |
| */ |
| public static JAXBContext getJAXBContext(TreeSet<String> contextPackages) throws JAXBException { |
| return getJAXBContext(contextPackages, new Holder<CONSTRUCTION_TYPE>(), |
| contextPackages.toString(), null, null); |
| } |
| |
| /** |
| * Get a JAXBContext for the class |
| * |
| * Note: The contextPackage object is used by multiple threads. It should be considered immutable |
| * and not altered by this method. |
| * |
| * @param contextPackage Set<Package> |
| * @param cacheKey ClassLoader |
| * @return JAXBContext |
| * @throws JAXBException |
| * @deprecated |
| */ |
| public static JAXBContext getJAXBContext(TreeSet<String> contextPackages, ClassLoader |
| cacheKey) throws JAXBException { |
| return getJAXBContext(contextPackages, new Holder<CONSTRUCTION_TYPE>(), |
| contextPackages.toString(), cacheKey, null); |
| } |
| |
| public static JAXBContext getJAXBContext(TreeSet<String> contextPackages, |
| Holder<CONSTRUCTION_TYPE> constructionType, |
| String key) |
| throws JAXBException { |
| return getJAXBContext(contextPackages, constructionType, key, null, null); |
| } |
| |
| /** |
| * Get a JAXBContext for the class |
| * |
| * Note: The contextPackage object is used by multiple threads. It should be considered immutable |
| * and not altered by this method. |
| * |
| * @param contextPackage Set<Package> |
| * @param contructionType (output value that indicates how the context was constructed) |
| * @param cacheKey ClassLoader |
| * @return JAXBContext |
| * @throws JAXBException |
| */ |
| public static JAXBContext getJAXBContext(TreeSet<String> contextPackages, |
| Holder<CONSTRUCTION_TYPE> constructionType, |
| String key, |
| ClassLoader cacheKey, |
| Map<String, ?> properties) |
| throws JAXBException { |
| // JAXBContexts for the same class can be reused and are supposed to be thread-safe |
| if (log.isDebugEnabled()) { |
| log.debug("Following packages are in this batch of getJAXBContext() :"); |
| for (String pkg : contextPackages) { |
| log.debug(pkg); |
| } |
| } |
| JAXBUtilsMonitor.addPackageKey(key); |
| |
| // Get or Create The InnerMap using the package key |
| ConcurrentHashMap<ClassLoader, JAXBContextValue> innerMap = null; |
| SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>> |
| softRef = jaxbMap.get(key); |
| |
| if (softRef != null) { |
| innerMap = softRef.get(); |
| } |
| |
| if (innerMap == null) { |
| synchronized(jaxbMap) { |
| softRef = jaxbMap.get(key); |
| if (softRef != null) { |
| innerMap = softRef.get(); |
| } |
| if (innerMap == null) { |
| innerMap = new ConcurrentHashMap<ClassLoader, JAXBContextValue>(); |
| softRef = |
| new SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>>(innerMap); |
| jaxbMap.put(key, softRef); |
| } |
| } |
| } |
| |
| // Now get the contextValue using either the classloader key or |
| // the current Classloader |
| ClassLoader cl = getContextClassLoader(); |
| JAXBContextValue contextValue = null; |
| if(cacheKey != null) { |
| if(log.isDebugEnabled()) { |
| log.debug("Using supplied classloader to retrieve JAXBContext: " + |
| cacheKey); |
| } |
| contextValue = innerMap.get(cacheKey); |
| } else { |
| if(log.isDebugEnabled()) { |
| log.debug("Using classloader from Thread to retrieve JAXBContext: " + |
| cl); |
| } |
| contextValue = innerMap.get(cl); |
| } |
| |
| |
| |
| if (contextPackages == null) { |
| contextPackages = new TreeSet<String>(); |
| } |
| if (contextValue == null) { |
| synchronized (innerMap) { |
| // Try to get the contextValue once more since sync was temporarily exited. |
| ClassLoader clKey = (cacheKey != null) ? cacheKey:cl; |
| contextValue = innerMap.get(clKey); |
| adjustPoolSize(innerMap); |
| if (contextValue==null) { |
| // Create a copy of the contextPackages. This new TreeSet will |
| // contain only the valid contextPackages. |
| // Note: The original contextPackage set is accessed by multiple |
| // threads and should not be altered. |
| |
| TreeSet<String> validContextPackages = new TreeSet<String>(contextPackages); |
| |
| ClassLoader tryCl = cl; |
| contextValue = createJAXBContextValue(validContextPackages, cl, properties); |
| |
| // If we don't get all the classes, try the cached classloader |
| if (cacheKey != null && validContextPackages.size() != contextPackages.size()) { |
| tryCl = cacheKey; |
| validContextPackages = new TreeSet<String>(contextPackages); |
| contextValue = createJAXBContextValue(validContextPackages, cacheKey, properties); |
| } |
| synchronized (jaxbMap) { |
| // Add the context value with the original package set |
| ConcurrentHashMap<ClassLoader, JAXBContextValue> map1 = null; |
| SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>> |
| softRef1 = jaxbMap.get(key); |
| if (softRef1 != null) { |
| map1 = softRef.get(); |
| } |
| if (map1 == null) { |
| map1 = new ConcurrentHashMap<ClassLoader, JAXBContextValue>(); |
| softRef1 = |
| new SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>>(map1); |
| jaxbMap.put(key, softRef1); |
| } |
| map1.put(clKey, contextValue); |
| |
| String validPackagesKey = validContextPackages.toString(); |
| |
| // Add the context value with the new package set |
| ConcurrentHashMap<ClassLoader, JAXBContextValue> map2 = null; |
| SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>> |
| softRef2 = jaxbMap.get(validPackagesKey); |
| if (softRef2 != null) { |
| map2 = softRef.get(); |
| } |
| if (map2 == null) { |
| map2 = new ConcurrentHashMap<ClassLoader, JAXBContextValue>(); |
| softRef2 = |
| new SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>>(map2); |
| jaxbMap.put(key, softRef2); |
| } |
| map2.put(clKey, contextValue); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("JAXBContext [created] for " + key); |
| log.debug("JAXBContext also stored by the list of valid packages:" + validPackagesKey); |
| } |
| } |
| } |
| } |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("JAXBContext [from pool] for " + key); |
| } |
| } |
| constructionType.value = contextValue.constructionType; |
| return contextValue.jaxbContext; |
| } |
| |
| /** |
| * Create a JAXBContext using the contextPackages |
| * |
| * @param contextPackages Set<String> |
| * @param cl ClassLoader |
| * @return JAXBContextValue (JAXBContext + constructionType) |
| * @throws JAXBException |
| */ |
| private static JAXBContextValue createJAXBContextValue(TreeSet<String> contextPackages, |
| ClassLoader cl, |
| Map<String, ?> properties) throws JAXBException { |
| |
| JAXBContextValue contextValue = null; |
| if (log.isDebugEnabled()) { |
| |
| log.debug("Following packages are in this batch of getJAXBContext() :"); |
| |
| for (String pkg : contextPackages) { |
| log.debug(pkg); |
| } |
| log.debug("This classloader will be used to construct the JAXBContext" + cl); |
| } |
| // The contextPackages is a set of package names that are constructed using PackageSetBuilder. |
| // PackageSetBuilder gets the packages names from various sources. |
| // a) It walks the various annotations on the WebService collecting package names. |
| // b) It walks the wsdl/schemas and builds package names for each target namespace. |
| // |
| // The combination of these two sources should produce all of the package names. |
| // ------------- |
| // Note that (b) is necessary for the following case: |
| // An operation has a parameter named BASE. |
| // Object DERIVED is an extension of BASE and is defined in a different package/schema. |
| // In this case, there will not be any annotations on the WebService that reference DERIVED. |
| // The only way to find the package for DERIVED is to walk the schemas. |
| // ------------- |
| |
| Iterator<String> it = contextPackages.iterator(); |
| while (it.hasNext()) { |
| String p = it.next(); |
| // Don't consider java and javax packages |
| // REVIEW: We might have to refine this |
| if (p.startsWith("javax.xml.ws.wsaddressing")) { |
| continue; |
| } |
| if (p.startsWith("java.") || |
| p.startsWith("javax.")) { |
| it.remove(); |
| } |
| } |
| |
| // There are two ways to construct the context. |
| // 1) USE A CONTEXTPATH, which is a string containing |
| // all of the packages separated by colons. |
| // 2) USE A CLASS[], which is an array of all of the classes |
| // involved in the marshal/unmarshal. |
| // |
| // There are pros/cons with both approaches. |
| // USE A CONTEXTPATH: |
| // Pros: preferred way of doing this. |
| // performant |
| // most dynamic |
| // Cons: Each package in context path must have an ObjectFactory |
| // |
| // |
| // USE CLASS[]: |
| // Pros: Doesn't require ObjectFactory in each package |
| // Cons: Hard to set up, must account for JAX-WS classes, etc. |
| // Does not work if arrays of classes are needed |
| // slower |
| // |
| // The following code attempts to build a context path. It then |
| // choose one of the two constructions above (prefer USE A CONTEXT_PATH) |
| // |
| |
| // The packages are examined to see if they have ObjectFactory/package-info classes. |
| // Invalid packages are removed from the list |
| it = contextPackages.iterator(); |
| boolean contextConstruction = true; |
| boolean isJAXBFound = false; |
| while (it.hasNext()) { |
| String p = it.next(); |
| // See if this package has an ObjectFactory or package-info |
| if (checkPackage(p, cl)) { |
| // Flow to here indicates package can be used for CONTEXT construction |
| isJAXBFound = true; |
| if (log.isDebugEnabled()) { |
| log.debug("Package " + p + " contains an ObjectFactory or package-info class."); |
| } |
| } else { |
| // Flow to here indicates that the package is not valid for context construction. |
| // Perhaps the package is invalid. |
| if (log.isDebugEnabled()) { |
| log.debug("Package " + p + |
| " does not contain an ObjectFactory or package-info class. Searching for JAXB classes"); |
| } |
| List<Class> classes = null; |
| classes = getAllClassesFromPackage(p, cl); |
| if (classes == null || classes.size() == 0) { |
| if (log.isDebugEnabled()) { |
| log.debug("Package " + p + |
| " does not have any JAXB classes. It is removed from the JAXB context path."); |
| } |
| it.remove(); |
| } else { |
| // Classes are found in the package. We cannot use the CONTEXT construction |
| contextConstruction = false; |
| if (log.isDebugEnabled()) { |
| log.debug("Package " + p + |
| " does not contain ObjectFactory, but it does contain other JAXB classes."); |
| } |
| } |
| } |
| } |
| |
| if (!isJAXBFound) { |
| if (log.isDebugEnabled()) { |
| log.debug("Both ObjectFactory & package-info not found in package hierachy"); |
| } |
| } |
| |
| // The code above may have removed some packages from the list. |
| // Retry our lookup with the updated list |
| String key = contextPackages.toString(); |
| ConcurrentHashMap<ClassLoader, JAXBContextValue> innerMap = null; |
| SoftReference<ConcurrentHashMap<ClassLoader, JAXBContextValue>> softRef = jaxbMap.get(key); |
| if (softRef != null) { |
| innerMap = softRef.get(); |
| } |
| |
| if (innerMap != null) { |
| contextValue = innerMap.get(cl); |
| if (contextValue != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Successfully found JAXBContext with updated context list:" + |
| contextValue.jaxbContext.toString()); |
| } |
| return contextValue; |
| } |
| } |
| |
| // CONTEXT construction |
| if (contextConstruction) { |
| JAXBContext context = createJAXBContextUsingContextPath(contextPackages, cl); |
| if (context != null) { |
| contextValue = new JAXBContextValue(context, CONSTRUCTION_TYPE.BY_CONTEXT_PATH); |
| } |
| } |
| |
| // CLASS construction |
| if (contextValue == null) { |
| it = contextPackages.iterator(); |
| List<Class> fullList = new ArrayList<Class>(); |
| while (it.hasNext()) { |
| String pkg = it.next(); |
| fullList.addAll(getAllClassesFromPackage(pkg, cl)); |
| } |
| //Lets add all common array classes |
| addCommonArrayClasses(fullList); |
| Class[] classArray = fullList.toArray(new Class[0]); |
| JAXBContext context = JAXBContext_newInstance(classArray, cl, properties); |
| if (context != null) { |
| contextValue = new JAXBContextValue(context, CONSTRUCTION_TYPE.BY_CLASS_ARRAY); |
| } |
| } |
| if (log.isDebugEnabled()) { |
| log.debug("Successfully created JAXBContext " + contextValue.jaxbContext.toString()); |
| } |
| return contextValue; |
| } |
| |
| /** |
| * Get the unmarshaller. You must call releaseUnmarshaller to put it back into the pool |
| * |
| * @param context JAXBContext |
| * @return Unmarshaller |
| * @throws JAXBException |
| */ |
| public static Unmarshaller getJAXBUnmarshaller(JAXBContext context) throws JAXBException { |
| if (!ENABLE_UNMARSHALL_POOLING) { |
| if (log.isDebugEnabled()) { |
| log.debug("Unmarshaller created [no pooling]"); |
| } |
| return internalCreateUnmarshaller(context); |
| } |
| Unmarshaller unm = upool.get(context); |
| if (unm == null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Unmarshaller created [not in pool]"); |
| } |
| unm = internalCreateUnmarshaller(context); |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("Unmarshaller obtained [from pool]"); |
| } |
| } |
| return unm; |
| } |
| |
| private static Unmarshaller internalCreateUnmarshaller(final JAXBContext context) throws JAXBException { |
| Unmarshaller unm; |
| try { |
| unm = (Unmarshaller) AccessController.doPrivileged( |
| new PrivilegedExceptionAction() { |
| public Object run() throws JAXBException { |
| return context.createUnmarshaller(); |
| } |
| } |
| ); |
| } catch (PrivilegedActionException e) { |
| throw (JAXBException) e.getCause(); |
| } |
| return unm; |
| } |
| |
| private static Marshaller internalCreateMarshaller(final JAXBContext context) throws JAXBException { |
| Marshaller marshaller; |
| try { |
| marshaller = (Marshaller) AccessController.doPrivileged( |
| new PrivilegedExceptionAction() { |
| public Object run() throws JAXBException { |
| return context.createMarshaller(); |
| } |
| } |
| ); |
| } catch (PrivilegedActionException e) { |
| throw (JAXBException) e.getCause(); |
| } |
| return marshaller; |
| } |
| |
| /** |
| * Release Unmarshaller Do not call this method if an exception occurred while using the |
| * Unmarshaller. We object my be in an invalid state. |
| * |
| * @param context JAXBContext |
| * @param unmarshaller Unmarshaller |
| */ |
| public static void releaseJAXBUnmarshaller(JAXBContext context, Unmarshaller unmarshaller) { |
| if (log.isDebugEnabled()) { |
| log.debug("Unmarshaller placed back into pool"); |
| } |
| if (ENABLE_UNMARSHALL_POOLING) { |
| unmarshaller.setAttachmentUnmarshaller(null); |
| upool.put(context, unmarshaller); |
| } |
| } |
| |
| /** |
| * Get JAXBMarshaller |
| * |
| * @param context JAXBContext |
| * @return Marshaller |
| * @throws JAXBException |
| */ |
| public static Marshaller getJAXBMarshaller(JAXBContext context) throws JAXBException { |
| Marshaller m = null; |
| if (!ENABLE_MARSHALL_POOLING) { |
| if (log.isDebugEnabled()) { |
| log.debug("Marshaller created [no pooling]"); |
| } |
| m = internalCreateMarshaller(context); |
| } else { |
| m = mpool.get(context); |
| if (m == null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Marshaller created [not in pool]"); |
| } |
| m = internalCreateMarshaller(context); |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("Marshaller obtained [from pool]"); |
| } |
| } |
| } |
| m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); // No PIs |
| return m; |
| } |
| |
| /** |
| * releaseJAXBMarshalller Do not call this method if an exception occurred while using the |
| * Marshaller. We object my be in an invalid state. |
| * |
| * @param context JAXBContext |
| * @param marshaller Marshaller |
| */ |
| public static void releaseJAXBMarshaller(JAXBContext context, Marshaller marshaller) { |
| if (log.isDebugEnabled()) { |
| log.debug("Marshaller placed back into pool"); |
| } |
| if (ENABLE_MARSHALL_POOLING) { |
| marshaller.setAttachmentMarshaller(null); |
| mpool.put(context, marshaller); |
| } |
| } |
| |
| /** |
| * get JAXB Introspector |
| * |
| * @param context JAXBContext |
| * @return JAXBIntrospector |
| * @throws JAXBException |
| */ |
| public static JAXBIntrospector getJAXBIntrospector(final JAXBContext context) throws JAXBException { |
| JAXBIntrospector i = null; |
| if (!ENABLE_INTROSPECTION_POOLING) { |
| if (log.isDebugEnabled()) { |
| log.debug("JAXBIntrospector created [no pooling]"); |
| } |
| i = internalCreateIntrospector(context); |
| } else { |
| i = ipool.get(context); |
| if (i == null) { |
| if (log.isDebugEnabled()) { |
| log.debug("JAXBIntrospector created [not in pool]"); |
| } |
| i = internalCreateIntrospector(context); |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("JAXBIntrospector obtained [from pool]"); |
| } |
| } |
| } |
| return i; |
| } |
| |
| private static JAXBIntrospector internalCreateIntrospector(final JAXBContext context) { |
| JAXBIntrospector i; |
| i = (JAXBIntrospector) AccessController.doPrivileged( |
| new PrivilegedAction() { |
| public Object run() { |
| return context.createJAXBIntrospector(); |
| } |
| } |
| ); |
| return i; |
| } |
| |
| /** |
| * Release JAXBIntrospector Do not call this method if an exception occurred while using the |
| * JAXBIntrospector. We object my be in an invalid state. |
| * |
| * @param context JAXBContext |
| * @param introspector JAXBIntrospector |
| */ |
| public static void releaseJAXBIntrospector(JAXBContext context, JAXBIntrospector introspector) { |
| if (log.isDebugEnabled()) { |
| log.debug("JAXBIntrospector placed back into pool"); |
| } |
| if (ENABLE_INTROSPECTION_POOLING) { |
| ipool.put(context, introspector); |
| } |
| } |
| |
| /** |
| * @param p Package |
| * @param cl |
| * @return true if each package has a ObjectFactory class or package-info |
| */ |
| private static boolean checkPackage(String p, ClassLoader cl) { |
| |
| // Each package must have an ObjectFactory |
| if (log.isDebugEnabled()) { |
| log.debug("checking package :" + p); |
| |
| } |
| try { |
| Class cls = forName(p + ".ObjectFactory", false, cl); |
| if (cls != null) { |
| return true; |
| } |
| //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that |
| //does not extend Exception. So we will absorb any Throwable exception here. |
| } catch (Throwable e) { |
| if (log.isDebugEnabled()) { |
| log.debug("ObjectFactory Class Not Found " + e); |
| log.trace("...caused by " + e.getCause() + " " + JavaUtils.stackToString(e)); |
| } |
| } |
| |
| try { |
| Class cls = forName(p + ".package-info", false, cl); |
| if (cls != null) { |
| return true; |
| } |
| //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that |
| //does not extend Exception. So we will absorb any Throwable exception here. |
| } catch (Throwable e) { |
| if (log.isDebugEnabled()) { |
| log.debug("package-info Class Not Found " + e); |
| log.trace("...caused by " + e.getCause() + " " + JavaUtils.stackToString(e)); |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Create a JAXBContext using the contextpath approach |
| * |
| * @param packages |
| * @param cl ClassLoader |
| * @return JAXBContext or null if unsuccessful |
| */ |
| private static JAXBContext createJAXBContextUsingContextPath(TreeSet<String> packages, |
| ClassLoader cl) { |
| JAXBContext context = null; |
| String contextpath = ""; |
| |
| // Iterate through the classes and build the contextpath |
| Iterator<String> it = packages.iterator(); |
| while (it.hasNext()) { |
| String p = it.next(); |
| if (contextpath.length() != 0) { |
| contextpath += ":"; |
| } |
| contextpath += p; |
| |
| } |
| try { |
| if (log.isDebugEnabled()) { |
| log.debug("Attempting to create JAXBContext with contextPath=" + contextpath); |
| } |
| context = JAXBContext_newInstance(contextpath, cl); |
| if (log.isDebugEnabled()) { |
| log.debug(" Successfully created JAXBContext:" + context); |
| } |
| } catch (Throwable e) { |
| if (log.isDebugEnabled()) { |
| log.debug( |
| " Unsuccessful: We will now use an alterative JAXBConstruct construction"); |
| log.debug(" Reason " + e.toString()); |
| } |
| } |
| return context; |
| } |
| |
| /** |
| * This method will return all the Class names needed to construct a JAXBContext |
| * |
| * @param pkg Package |
| * @param ClassLoader cl |
| * @return |
| * @throws ClassNotFoundException if error occurs getting package |
| */ |
| private static List<Class> getAllClassesFromPackage(String pkg, ClassLoader cl) { |
| if (pkg == null) { |
| return new ArrayList<Class>(); |
| } |
| |
| // See if this is a special package that has a set of known classes. |
| List<Class> knownClasses = specialMap.get(pkg); |
| if (knownClasses != null) { |
| return knownClasses; |
| } |
| |
| /* |
| * This method is a best effort method. We should always return an object. |
| */ |
| |
| ArrayList<Class> classes = new ArrayList<Class>(); |
| |
| try { |
| // This will load classes from directory |
| List<Class> classesFromDir = getClassesFromDirectory(pkg, cl); |
| checkClasses(classesFromDir, pkg); |
| classes.addAll(classesFromDir); |
| } catch (ClassNotFoundException e) { |
| if (log.isDebugEnabled()) { |
| log.debug("getClassesFromDirectory failed to get Classes"); |
| } |
| } |
| try { |
| //If Clases not found in directory then look for jar that has these classes |
| if (classes.size() <= 0) { |
| //This will load classes from jar file. |
| ClassFinderFactory cff = |
| (ClassFinderFactory)FactoryRegistry.getFactory(ClassFinderFactory.class); |
| ClassFinder cf = cff.getClassFinder(); |
| |
| List<Class> classesFromJar = cf.getClassesFromJarFile(pkg, cl); |
| |
| checkClasses(classesFromJar, pkg); |
| classes.addAll(classesFromJar); |
| } |
| } catch (ClassNotFoundException e) { |
| if (log.isDebugEnabled()) { |
| log.debug("getClassesFromJarFile failed to get Classes"); |
| } |
| } |
| |
| return classes; |
| } |
| |
| /** |
| * @param list |
| * @param pkg |
| */ |
| private static void checkClasses(List<Class> list, String pkg) { |
| // The installed classfinder or directory search may inadvertently add too many |
| // classes. This rountine is a 'double check' to make sure that the classes |
| // are acceptable. |
| for (int i=0; i<list.size();) { |
| Class cls = list.get(i); |
| if (!cls.isInterface() && |
| (cls.isEnum() || |
| getAnnotation(cls, XmlType.class) != null || |
| ClassUtils.getDefaultPublicConstructor(cls) != null) && |
| !ClassUtils.isJAXWSClass(cls) && |
| cls.getPackage().getName().equals(pkg)) { |
| i++; // Acceptable class |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("Removing class " + cls + " from consideration because it is not in package " + pkg + |
| " or is an interface or does not have a public constructor or is" + |
| " a jaxws class"); |
| } |
| list.remove(i); |
| } |
| } |
| } |
| |
| private static ArrayList<Class> getClassesFromDirectory(String pkg, ClassLoader cl) |
| throws ClassNotFoundException { |
| // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths |
| String pckgname = pkg; |
| ArrayList<File> directories = new ArrayList<File>(); |
| try { |
| String path = pckgname.replace('.', '/'); |
| // Ask for all resources for the path |
| Enumeration<URL> resources = cl.getResources(path); |
| while (resources.hasMoreElements()) { |
| directories.add(new File( |
| URLDecoder.decode(resources.nextElement().getPath(), "UTF-8"))); |
| } |
| } catch (UnsupportedEncodingException e) { |
| if (log.isDebugEnabled()) { |
| log.debug( |
| pckgname + " does not appear to be a valid package (Unsupported encoding)"); |
| } |
| throw new ClassNotFoundException(Messages.getMessage("ClassUtilsErr2", pckgname)); |
| } catch (IOException e) { |
| if (log.isDebugEnabled()) { |
| log.debug( |
| "IOException was thrown when trying to get all resources for " + pckgname); |
| } |
| throw new ClassNotFoundException(Messages.getMessage("ClassUtilsErr3", pckgname)); |
| } |
| |
| ArrayList<Class> classes = new ArrayList<Class>(); |
| // For every directory identified capture all the .class files |
| for (File directory : directories) { |
| if (log.isDebugEnabled()) { |
| log.debug(" Adding JAXB classes from directory: " + directory.getName()); |
| } |
| if (directory.exists()) { |
| // Get the list of the files contained in the package |
| String[] files = directory.list(); |
| for (String file : files) { |
| // we are only interested in .class files |
| if (file.endsWith(".class")) { |
| // removes the .class extension |
| // TODO Java2 Sec |
| String className = pckgname + '.' + file.substring(0, file.length() - 6); |
| try { |
| Class clazz = forName(className, |
| false, getContextClassLoader()); |
| // Don't add any interfaces or JAXWS specific classes. |
| // Only classes that represent data and can be marshalled |
| // by JAXB should be added. |
| if (!clazz.isInterface() |
| && (clazz.isEnum() || |
| getAnnotation(clazz, XmlType.class) != null || |
| ClassUtils.getDefaultPublicConstructor(clazz) != null) |
| && !ClassUtils.isJAXWSClass(clazz) |
| && !java.lang.Exception.class.isAssignableFrom(clazz)) { |
| |
| // Ensure that all the referenced classes are loadable too |
| clazz.getDeclaredMethods(); |
| clazz.getDeclaredFields(); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("Adding class: " + file); |
| } |
| classes.add(clazz); |
| |
| // REVIEW: |
| // Support of RPC list (and possibly other scenarios) requires that the array classes should also be present. |
| // This is a hack until we can determine how to get this information. |
| |
| // The arrayName and loadable name are different. Get the loadable |
| // name, load the array class, and add it to our list |
| //className += "[]"; |
| //String loadableName = ClassUtils.getLoadableClassName(className); |
| |
| //Class aClazz = Class.forName(loadableName, false, Thread.currentThread().getContextClassLoader()); |
| } |
| //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that |
| //does not extend Exception |
| } catch (Throwable e) { |
| if (log.isDebugEnabled()) { |
| log.debug("Tried to load class " + className + |
| " while constructing a JAXBContext. This class will be skipped. Processing Continues."); |
| log.debug(" The reason that class could not be loaded:" + |
| e.toString()); |
| log.trace(JavaUtils.stackToString(e)); |
| } |
| } |
| |
| } |
| } |
| } |
| } |
| |
| return classes; |
| } |
| |
| private static String[] commonArrayClasses = new String[] { |
| // primitives |
| "boolean[]", |
| "byte[]", |
| "char[]", |
| "double[]", |
| "float[]", |
| "int[]", |
| "long[]", |
| "short[]", |
| "java.lang.String[]", |
| // Others |
| "java.lang.Object[]", |
| "java.awt.Image[]", |
| "java.math.BigDecimal[]", |
| "java.math.BigInteger[]", |
| "java.util.Calendar[]", |
| "javax.xml.namespace.QName[]" }; |
| |
| private static void addCommonArrayClasses(List<Class> list) { |
| // Add common primitives arrays (necessary for RPC list type support) |
| ClassLoader cl = getContextClassLoader(); |
| |
| |
| for (int i = 0; i < commonArrayClasses.length; i++) { |
| String className = commonArrayClasses[i]; |
| try { |
| // Load and add the class |
| Class cls = forName(ClassUtils.getLoadableClassName(className), false, cl); |
| list.add(cls); |
| //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that |
| //does not extend Exception |
| } catch (Throwable e) { |
| if (log.isDebugEnabled()) { |
| log.debug("Tried to load class " + className + |
| " while constructing a JAXBContext. This class will be skipped. Processing Continues."); |
| log.debug(" The reason that class could not be loaded:" + e.toString()); |
| log.trace(JavaUtils.stackToString(e)); |
| } |
| } |
| } |
| } |
| |
| /** @return ClassLoader */ |
| private static ClassLoader getContextClassLoader() { |
| // NOTE: This method must remain private because it uses AccessController |
| ClassLoader cl = null; |
| try { |
| cl = (ClassLoader)AccessController.doPrivileged( |
| new PrivilegedExceptionAction() { |
| public Object run() throws ClassNotFoundException { |
| return Thread.currentThread().getContextClassLoader(); |
| } |
| } |
| ); |
| } catch (PrivilegedActionException e) { |
| if (log.isDebugEnabled()) { |
| log.debug("Exception thrown from AccessController: " + e); |
| } |
| throw ExceptionFactory.makeWebServiceException(e.getException()); |
| } |
| |
| return cl; |
| } |
| |
| /** |
| * Return the class for this name |
| * |
| * @return Class |
| */ |
| private static Class forName(final String className, final boolean initialize, |
| final ClassLoader classloader) throws ClassNotFoundException { |
| // NOTE: This method must remain private because it uses AccessController |
| Class cl = null; |
| try { |
| cl = (Class)AccessController.doPrivileged( |
| new PrivilegedExceptionAction() { |
| public Object run() throws ClassNotFoundException { |
| // Class.forName does not support primitives |
| Class cls = ClassUtils.getPrimitiveClass(className); |
| if (cls == null) { |
| cls = Class.forName(className, initialize, classloader); |
| } |
| return cls; |
| } |
| } |
| ); |
| } catch (PrivilegedActionException e) { |
| if (log.isDebugEnabled()) { |
| log.debug("Exception thrown from AccessController: " + e); |
| } |
| throw (ClassNotFoundException)e.getException(); |
| } |
| |
| return cl; |
| } |
| |
| /** |
| * Create JAXBContext from context String and ClassLoader |
| * |
| * @param context |
| * @param classloader |
| * @return |
| * @throws Exception |
| */ |
| private static JAXBContext JAXBContext_newInstance(final String context, |
| final ClassLoader classloader) |
| throws Exception { |
| // NOTE: This method must remain private because it uses AccessController |
| JAXBContext jaxbContext = null; |
| try { |
| if (log.isDebugEnabled()) { |
| if (context == null || context.length() == 0) { |
| log.debug("JAXBContext is constructed without a context String."); |
| } else { |
| log.debug("JAXBContext is constructed with a context of:" + context); |
| } |
| } |
| jaxbContext = (JAXBContext)AccessController.doPrivileged( |
| new PrivilegedExceptionAction() { |
| public Object run() throws JAXBException { |
| return JAXBContext.newInstance(context, classloader); |
| } |
| } |
| ); |
| } catch (PrivilegedActionException e) { |
| if (log.isDebugEnabled()) { |
| log.debug("Exception thrown from AccessController: " + e); |
| } |
| throw e.getException(); |
| } |
| |
| return jaxbContext; |
| } |
| |
| /** |
| * Create JAXBContext from Class[] |
| * |
| * @param classArray |
| * @param cl ClassLoader that loaded the classes |
| * @return |
| * @throws Exception |
| */ |
| private static JAXBContext JAXBContext_newInstance(final Class[] classArray, |
| final ClassLoader cl, |
| Map<String, ?> properties) |
| throws JAXBException { |
| // NOTE: This method must remain private because it uses AccessController |
| JAXBContext jaxbContext = null; |
| |
| if (log.isDebugEnabled()) { |
| if (classArray == null || classArray.length == 0) { |
| log.debug("JAXBContext is constructed with 0 input classes."); |
| } else { |
| log.debug("JAXBContext is constructed with " + classArray.length + |
| " input classes."); |
| } |
| } |
| |
| // Get JAXBContext from classes |
| jaxbContext = JAXBContextFromClasses.newInstance(classArray, cl, properties); |
| |
| return jaxbContext; |
| } |
| |
| /** Holds the JAXBContext and the manner by which it was constructed */ |
| static class JAXBContextValue { |
| |
| public JAXBContext jaxbContext; |
| public CONSTRUCTION_TYPE constructionType; |
| |
| public JAXBContextValue(JAXBContext jaxbContext, CONSTRUCTION_TYPE constructionType) { |
| this.jaxbContext = jaxbContext; |
| this.constructionType = constructionType; |
| } |
| } |
| |
| static private void adjustPoolSize(Map map) { |
| if (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; |
| } |
| } |
| } |
| |
| /** |
| * Pool a list of items for a specific key |
| * |
| * @param <K> Key |
| * @param <V> Pooled object |
| */ |
| private static class Pool<K,V> { |
| private SoftReference<Map<K,List<V>>> softMap = |
| new SoftReference<Map<K,List<V>>>( |
| new ConcurrentHashMap<K, List<V>>()); |
| |
| // 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. |
| */ |
| private List<V> getValues(K key) { |
| Map<K,List<V>> map = softMap.get(); |
| List<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, List<V>>(); |
| softMap = |
| new SoftReference<Map<K,List<V>>>(map); |
| } |
| values = new ArrayList<V>(); |
| map.put(key, values); |
| |
| } |
| return values; |
| } |
| } |
| |
| /** |
| * AdjustSize |
| * 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() { |
| Map<K,List<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; |
| } |
| } |
| } |
| } |
| |
| private static Annotation getAnnotation(final AnnotatedElement element, final Class annotation) { |
| return (Annotation) AccessController.doPrivileged(new PrivilegedAction() { |
| public Object run() { |
| return element.getAnnotation(annotation); |
| } |
| }); |
| } |
| |
| private static String getDefaultNamespaceRemapProperty() { |
| String external = "com.sun.xml.bind.defaultNamespaceRemap"; |
| String internal = "com.sun.xml.internal.bind.defaultNamespaceRemap"; |
| |
| Boolean isExternal = testJAXBProperty(external); |
| if (Boolean.TRUE.equals(isExternal)) { |
| return external; |
| } |
| Boolean isInternal = testJAXBProperty(internal); |
| if (Boolean.TRUE.equals(isInternal)) { |
| return internal; |
| } |
| // hmm... both properties cannot be set |
| return external; |
| } |
| |
| private static Boolean testJAXBProperty(String propName) { |
| final Map<String, String> props = new HashMap<String, String>(); |
| props.put(propName, "http://test"); |
| try { |
| AccessController.doPrivileged( |
| new PrivilegedExceptionAction() { |
| public Object run() throws JAXBException { |
| return JAXBContext.newInstance(new Class[] {Integer.class}, props); |
| } |
| }); |
| return Boolean.TRUE; |
| } catch (PrivilegedActionException e) { |
| if (e.getCause() instanceof JAXBException) { |
| return Boolean.FALSE; |
| } |
| return null; |
| } |
| } |
| } |