/* * 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.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import javax.xml.namespace.QName; import java.awt.Image; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.math.BigDecimal; import java.math.BigInteger; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Map; /** * This class provides a utility method, newInstance, which * builds a valid JAXBContext from a series of classes. */ public class JAXBContextFromClasses { private static final Log log = LogFactory.getLog(JAXBContextFromClasses.class); /** * Static utility class. Constructor is intentionally private */ private JAXBContextFromClasses() { } /** * Create a JAXBContext from the given class array and class loader. * If errors occur, then the JAXBContext is created from the * minimal set of valid classes. * * Note: Sometimes users will intermingle JAXB classes and other * non-JAXB utility classes. This is not a good practice, but does * happen. The purpose of this method is to try and build a valid * JAXBContext from only the 'valid' classes. * * @param classArray * @param cl * @param properties * @param classRefs optional List<String> of class references * @return JAXBContext * @throws JAXBException */ public static JAXBContext newInstance(Class[] classArray, ClassLoader cl, Map<String, ?> properties) throws JAXBException { return newInstance(classArray, cl, properties, new ArrayList<String>()); } public static JAXBContext newInstance(Class[] classArray, ClassLoader cl, Map<String, ?> properties, List<String> classRefs) throws JAXBException { JAXBContext jaxbContext = null; try { if (log.isDebugEnabled()) { if (classArray == null || classArray.length == 0) { log.debug("Try to construct JAXBContext with 0 input classes."); } else { log.debug("Try to construct JAXBContext with " + classArray.length + " input classes."); } } jaxbContext = _newInstance(classArray, cl, properties); if (log.isDebugEnabled()) { log.debug("Successfully constructed JAXBContext " + jaxbContext); } } catch (Throwable t) { // Try finding the best set of classes ArrayList<Class> original = new ArrayList<Class>(); for (int i=0; i < classArray.length; i++) { original.add(classArray[i]); } ArrayList<Class> best = new ArrayList<Class>(); jaxbContext = findBestSet(original, cl, best, properties, classRefs); } return jaxbContext; } /** * Utility method that creates a JAXBContext from the * class[] and ClassLoader. * * @param classArray * @param cl * @return JAXBContext * @throws Throwable */ private static JAXBContext _newInstance(final Class[] classArray, final ClassLoader cl, final Map<String, ?> properties) throws Throwable { JAXBContext jaxbContext; try { jaxbContext = (JAXBContext)AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws JAXBException { // Unlike the JAXBContext.newInstance(Class[]) method // does now accept a classloader. To workaround this // issue, the classloader is temporarily changed to cl Thread currentThread = Thread.currentThread(); ClassLoader savedClassLoader = currentThread.getContextClassLoader(); try { currentThread.setContextClassLoader(cl); return JAXBContext.newInstance(classArray, properties); } finally { currentThread.setContextClassLoader(savedClassLoader); } } } ); } catch (PrivilegedActionException e) { throw ((PrivilegedActionException) e).getException(); } catch (Throwable t) { throw t; } return jaxbContext; } /** * Utility class that quickly divides a list of classes into two categories. * The primary category classes contains classes that are referenced. * The secondary category classes are the remaining classes * @param original * @param primary * @param secondary */ static void separate(List<Class> original, List<Class> primary, List<Class> secondary, List<String> classRefs) { for (int i=0; i<original.size(); i++) { Class cls = original.get(i); String clsName = cls.getCanonicalName(); if (commonArrayClasses.contains(cls)) { if (log.isDebugEnabled()) { log.debug("This looks like a JAXB common class. Adding it to primary list:" + cls.getName()); } primary.add(cls); } else if (classRefs.contains(clsName)) { if (log.isDebugEnabled()) { log.debug("This is a referenced class. Adding it to primary list:" + clsName); } Package pkg = cls.getPackage(); if (pkg != null && pkg.getName().endsWith(".jaxws")) { if (log.isDebugEnabled()) { log.debug("This looks like a jaxws generated Class. Adding it to the front of the primary list:" + cls.getName()); } primary.add(0,cls); // Add to the front of the list } else { primary.add(cls); } } else { if (log.isDebugEnabled()) { log.debug("This class is not referenced by the web service. Adding it to secondary list:" + cls.getName()); } Package pkg = cls.getPackage(); if (pkg != null && pkg.getName().endsWith(".jaxws")) { if (log.isDebugEnabled()) { log.debug("This looks like a jaxws generated Class. Adding it to the front of the secondary list:" + cls.getName()); } secondary.add(0,cls); // Add to the front of the list } else { secondary.add(cls); } } } } /** * Given a list of classes, this method determines the best minimal set * of classes and returns the JAXBContext for this minimal set. * @param original List<Class> * @param cl ClassLoader * @param List<Class> is populated with the minimal, best set of classes * @param List<String> input list of classes that are directly referenced in the web service api * @return JAXBContext */ static JAXBContext findBestSet(List<Class> original, ClassLoader cl, List<Class> best, Map<String, ?> properties, List<String> classRefs) { if (log.isDebugEnabled()) { log.debug("Could not construct JAXBContext with the initial list."); log.debug("Now trying to construct JAXBContext with only the valid classes in the list"); } JAXBContext jc = null; Class[] clsArray = new Class[0]; // Divide the list into the classes that are referenced (primary) // and the rest (secondary) ArrayList<Class> primary = new ArrayList<Class> (); ArrayList<Class> secondary = new ArrayList<Class> (); separate(original, primary, secondary, classRefs); // Prime the pump // Build a JAXBContext with the primary classes best.addAll(primary); if (best.size() > 0) { try { jc = _newInstance(best.toArray(clsArray), cl, properties); } catch (Throwable t) { if (log.isDebugEnabled()) { log.debug("The JAXBContext creation failed with the primary list"); log.debug("Will try a more brute force algorithm"); log.debug(" The reason is " + t); } // Add all of the primary classes to the secondary list so // that we can walk them one by one. secondary.addAll(primary); best.clear(); // Clear out the best list } } // Now add secondary classes one at a time. // If the JAXBContext creation is successful, add the class // to the best list. Otherwise continue. // // @REVIEW One optimization is to do a toString() on the JAXBContext // and check for the presense of the secondary class. This would // save time. However, this also assumes that the toString() // of JAXBContext does not change. for (int i = 0; i<secondary.size(); i++) { Class cls = secondary.get(i); best.add(cls); try { jc = _newInstance(best.toArray(clsArray), cl, properties); } catch (Throwable t) { if (log.isDebugEnabled()) { log.debug("The following class is not a JAXB class: " + cls.getCanonicalName()); log.debug(" JAXBContext creation continues without this class."); log.debug(" The reason is " + t); } best.remove(cls); // @REVIEW // We could save the exceptions (perhaps in a list) and // return the exceptions. Then if problems occur later // (i.e. do to missing exceptions) then we could throw // this exception. } } return jc; } /** * Get an annotation. This is wrappered to avoid a Java2Security violation. * @param cls Class that contains annotation * @param annotation Class of requrested Annotation * @return annotation or null */ 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 List<Class> commonArrayClasses = new ArrayList<Class>(); static { commonArrayClasses.add(boolean[].class); commonArrayClasses.add(byte[].class); commonArrayClasses.add(char[].class); commonArrayClasses.add(double[].class); commonArrayClasses.add(float[].class); commonArrayClasses.add(int[].class); commonArrayClasses.add(long[].class); commonArrayClasses.add(short[].class); commonArrayClasses.add(String[].class); commonArrayClasses.add(Object[].class); commonArrayClasses.add(Image[].class); commonArrayClasses.add(BigDecimal[].class); commonArrayClasses.add(BigInteger[].class); commonArrayClasses.add(Calendar[].class); commonArrayClasses.add(QName[].class); } }