blob: 67714e9ac4b884844a4137f33fb27d38fa45e12c [file] [log] [blame]
/*
* 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;
}
}
}