You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicemix.apache.org by ni...@apache.org on 2011/01/05 10:27:11 UTC

svn commit: r1055371 - /servicemix/smx4/specs/trunk/jaxb-api-2.2/src/main/java/javax/xml/bind/ContextFinder.java

Author: ningjiang
Date: Wed Jan  5 09:27:11 2011
New Revision: 1055371

URL: http://svn.apache.org/viewvc?rev=1055371&view=rev
Log:
SMX4-734 Updated the jaxb 2.2 spec code

Modified:
    servicemix/smx4/specs/trunk/jaxb-api-2.2/src/main/java/javax/xml/bind/ContextFinder.java

Modified: servicemix/smx4/specs/trunk/jaxb-api-2.2/src/main/java/javax/xml/bind/ContextFinder.java
URL: http://svn.apache.org/viewvc/servicemix/smx4/specs/trunk/jaxb-api-2.2/src/main/java/javax/xml/bind/ContextFinder.java?rev=1055371&r1=1055370&r2=1055371&view=diff
==============================================================================
--- servicemix/smx4/specs/trunk/jaxb-api-2.2/src/main/java/javax/xml/bind/ContextFinder.java (original)
+++ servicemix/smx4/specs/trunk/jaxb-api-2.2/src/main/java/javax/xml/bind/ContextFinder.java Wed Jan  5 09:27:11 2011
@@ -16,168 +16,496 @@
  */
 package javax.xml.bind;
 
-import java.lang.reflect.Method;
-import java.util.Map;
-import java.util.Properties;
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.BufferedReader;
 import java.io.InputStreamReader;
-
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import static javax.xml.bind.JAXBContext.JAXB_CONTEXT_FACTORY;
+
+//import java.lang.reflect.InvocationTargetException;
+
+/**
+ * This class is package private and therefore is not exposed as part of the 
+ * JAXB API.
+ *
+ * This code is designed to implement the JAXB 1.0 spec pluggability feature
+ *
+ * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
+ * @see JAXBContext
+ */
 class ContextFinder {
+    private static final Logger logger;
+    static {
+        logger = Logger.getLogger("javax.xml.bind");
+        try {
+            if (AccessController.doPrivileged(new GetPropertyAction("jaxb.debug")) != null) {
+                // disconnect the logger from a bigger framework (if any)
+                // and take the matters into our own hands
+                logger.setUseParentHandlers(false);
+                logger.setLevel(Level.ALL);
+                ConsoleHandler handler = new ConsoleHandler();
+                handler.setLevel(Level.ALL);
+                logger.addHandler(handler);
+            } else {
+                // don't change the setting of this logger
+                // to honor what other frameworks
+                // have done on configurations.
+            }
+        } catch(Throwable t) {
+            // just to be extra safe. in particular System.getProperty may throw
+            // SecurityException.
+        }
+    }
 
-    private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.bind.v2.ContextFactory";
-    private static final String JAXB_CONTEXT_PROPERTY = JAXBContext.class.getName();
-    private static final String JAXB_CONTEXT_FACTORY = JAXBContext.JAXB_CONTEXT_FACTORY;
-
-    public static JAXBContext find(String factoryId, String contextPath, ClassLoader classLoader, Map properties) throws JAXBException {
-        String className = null;
-        // Patch for bug https://issues.apache.org/activemq/browse/SMX4-329
-        if (contextPath == null || contextPath.length() == 0) {
-            throw new JAXBException("Invalid contextPath (empty or null)");
-  	    }
-        String[] packages = contextPath.split(":");
-        if (packages == null || packages.length == 0) {
-            throw new JAXBException("Invalid contextPath (no packages)");
-        }
-        for (String pkg : packages) {
-            String url = pkg.replace('.', '/') + "/jaxb.properties";
-            className = loadClassNameFromProperties(url, classLoader);
-            if (className != null) {
-                break;
-            }
-        }
-        if (className == null) {
-            className = System.getProperty(JAXB_CONTEXT_PROPERTY);
-        }
-        if (className == null) {
-            String url = "META-INF/services/" + JAXB_CONTEXT_PROPERTY;
-            className = loadClassName(url, classLoader);
+    /**
+     * If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped,
+     * throw the wrapped exception.
+     */
+    private static void handleInvocationTargetException(InvocationTargetException x) throws JAXBException {
+        Throwable t = x.getTargetException();
+        if( t != null ) {
+            if( t instanceof JAXBException )
+                // one of our exceptions, just re-throw
+                throw (JAXBException)t;
+            if( t instanceof RuntimeException )
+                // avoid wrapping exceptions unnecessarily
+                throw (RuntimeException)t;
+            if( t instanceof Error )
+                throw (Error)t;
         }
-        if (className == null) {
-            className = PLATFORM_DEFAULT_FACTORY_CLASS;
+    }
+
+
+    /**
+     * Determine if two types (JAXBContext in this case) will generate a ClassCastException.
+     *
+     * For example, (targetType)originalType
+     *
+     * @param originalType
+     *          The Class object of the type being cast
+     * @param targetType
+     *          The Class object of the type that is being cast to
+     * @return JAXBException to be thrown.
+     */
+    private static JAXBException handleClassCastException(Class originalType, Class targetType) {
+        final URL targetTypeURL = which(targetType);
+
+        return new JAXBException(Messages.format(Messages.ILLEGAL_CAST,
+                // we don't care where the impl class is, we want to know where JAXBContext lives in the impl
+                // class' ClassLoader
+                originalType.getClassLoader().getResource("javax/xml/bind/JAXBContext.class"),
+                targetTypeURL));
+    }
+
+    /**
+     * Create an instance of a class using the specified ClassLoader
+     */
+    static JAXBContext newInstance( String contextPath,
+                               String className, 
+                               ClassLoader classLoader,
+                               Map properties )
+        throws JAXBException
+    {
+        try {
+            Class spiClass = safeLoadClass(className,classLoader);
+
+            /*
+             * javax.xml.bind.context.factory points to a class which has a
+             * static method called 'createContext' that
+             * returns a javax.xml.JAXBContext.
+             */
+
+            Object context = null;
+
+            // first check the method that takes Map as the third parameter.
+            // this is added in 2.0.
+            try {
+                Method m = spiClass.getMethod("createContext",String.class,ClassLoader.class,Map.class);
+                // any failure in invoking this method would be considered fatal
+                context = m.invoke(null,contextPath,classLoader,properties);
+            } catch (NoSuchMethodException e) {
+                // it's not an error for the provider not to have this method.
+            }
+
+            if(context==null) {
+                // try the old method that doesn't take properties. compatible with 1.0.
+                // it is an error for an implementation not to have both forms of the createContext method.
+                Method m = spiClass.getMethod("createContext",String.class,ClassLoader.class);
+                // any failure in invoking this method would be considered fatal
+                context = m.invoke(null,contextPath,classLoader);
+            }
+
+            if(!(context instanceof JAXBContext)) {
+                // the cast would fail, so generate an exception with a nice message
+                throw handleClassCastException(context.getClass(), JAXBContext.class);
+            }
+            return (JAXBContext)context;
+        } catch (ClassNotFoundException x) {
+            throw new JAXBException(
+                Messages.format( Messages.PROVIDER_NOT_FOUND, className ),
+                x);
+        } catch (InvocationTargetException x) {
+            handleInvocationTargetException(x);
+            // for other exceptions, wrap the internal target exception
+            // with a JAXBException
+            Throwable e = x;
+            if(x.getTargetException()!=null)
+                e = x.getTargetException();
+
+            throw new JAXBException( Messages.format( Messages.COULD_NOT_INSTANTIATE, className, e ), e );
+        } catch (RuntimeException x) {
+            // avoid wrapping RuntimeException to JAXBException,
+            // because it indicates a bug in this code.
+            throw x;
+        } catch (Exception x) {
+            // can't catch JAXBException because the method is hidden behind
+            // reflection.  Root element collisions detected in the call to
+            // createContext() are reported as JAXBExceptions - just re-throw it
+            // some other type of exception - just wrap it
+            throw new JAXBException(
+                Messages.format( Messages.COULD_NOT_INSTANTIATE, className, x ),
+                x);
         }
-        Class spi = loadSpi(className, classLoader);
+    }
+
+
+    /**
+     * Create an instance of a class using the specified ClassLoader
+     */
+    static JAXBContext newInstance(
+                              Class[] classes,
+                              Map properties,
+                              String className) throws JAXBException {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        Class spi;
         try {
-            Method m = spi.getMethod("createContext", new Class[] { String.class, ClassLoader.class, Map.class });
-            return (JAXBContext) m.invoke(null, new Object[] { contextPath, classLoader, properties });
+            spi = safeLoadClass(className,cl);
+        } catch (ClassNotFoundException e) {
+            throw new JAXBException(e);
+        }
+
+        if(logger.isLoggable(Level.FINE)) {
+            // extra check to avoid costly which operation if not logged
+            logger.fine("loaded "+className+" from "+which(spi));
+        }
+
+        Method m;
+        try {
+            m = spi.getMethod("createContext", Class[].class, Map.class);
         } catch (NoSuchMethodException e) {
-            // ignore
-        } catch (Throwable t) {
-            throw new JAXBException("Unable to create context", t);
-        } 
+            throw new JAXBException(e);
+        }
         // Fallback for JAXB 1.0 compatibility (at least JAXB TCK tests are using that feature)
         try {
-            Method m = spi.getMethod("createContext", new Class[] { String.class, ClassLoader.class, });
-            return (JAXBContext) m.invoke(null, new Object[] { contextPath, classLoader });
-        } catch (Throwable t) {
-            throw new JAXBException("Unable to create context", t);
+            Object context = m.invoke(null, classes, properties);
+            if(!(context instanceof JAXBContext)) {
+                // the cast would fail, so generate an exception with a nice message
+                throw handleClassCastException(context.getClass(), JAXBContext.class);
+            }
+            return (JAXBContext)context;
+        } catch (IllegalAccessException e) {
+            throw new JAXBException(e);
+        } catch (InvocationTargetException e) {
+            handleInvocationTargetException(e);
+
+            Throwable x = e;
+            if (e.getTargetException() != null)
+                x = e.getTargetException();
+
+            throw new JAXBException(x);
         }
     }
 
 
-    public static JAXBContext find(Class[] classes, Map properties) throws JAXBException {
-        String className = null;
-        for (Class cl : classes) {
-            Package pkg = cl.getPackage();
-            if (pkg != null) {
-                String url = pkg.getName().replace('.', '/') + "/jaxb.properties";
-                className = loadClassNameFromProperties(url, cl.getClassLoader());
-                if (className != null) {
-                    break;
+    static JAXBContext find(String factoryId, String contextPath, ClassLoader classLoader, Map properties ) throws JAXBException {
+
+        // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP?
+
+        final String jaxbContextFQCN = JAXBContext.class.getName();
+
+        // search context path for jaxb.properties first
+        StringBuilder propFileName;
+        StringTokenizer packages = new StringTokenizer( contextPath, ":" );
+        String factoryClassName;
+
+        if(!packages.hasMoreTokens())
+            // no context is specified
+            throw new JAXBException(Messages.format(Messages.NO_PACKAGE_IN_CONTEXTPATH));
+
+
+        logger.fine("Searching jaxb.properties");
+
+        while( packages.hasMoreTokens() ) {
+            String packageName = packages.nextToken(":").replace('.','/');
+            // com.acme.foo - > com/acme/foo/jaxb.properties
+             propFileName = new StringBuilder().append(packageName).append("/jaxb.properties");
+
+            Properties props = loadJAXBProperties( classLoader, propFileName.toString() );
+            if (props != null) {
+                if (props.containsKey(factoryId)) {
+                    factoryClassName = props.getProperty(factoryId);
+                    return newInstance( contextPath, factoryClassName, classLoader, properties );
+                } else {
+                    throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, factoryId));
                 }
             }
         }
-        if (className == null) {
-            className = System.getProperty(JAXB_CONTEXT_PROPERTY);
-        }
-        if (className == null) {
-            String url = "META-INF/services/" + JAXB_CONTEXT_PROPERTY;
-            className = loadClassName(url, Thread.currentThread().getContextClassLoader());
-        }
-        if (className == null) {
-            className = PLATFORM_DEFAULT_FACTORY_CLASS;
-        }
-        Class spi = loadSpi(className, Thread.currentThread().getContextClassLoader());
-        try {
-            Method m = spi.getMethod("createContext", new Class[] { Class[].class, Map.class });
-            return (JAXBContext) m.invoke(null, new Object[] { classes, properties });
-        } catch (Throwable t) {
-            throw new JAXBException("Unable to create context", t);
+
+        logger.fine("Searching the system property");
+
+        // search for a system property second (javax.xml.bind.JAXBContext)
+        factoryClassName = AccessController.doPrivileged(new GetPropertyAction(jaxbContextFQCN));
+        if(  factoryClassName != null ) {
+            return newInstance( contextPath, factoryClassName, classLoader, properties );
         }
-    }
 
-    private static String loadClassNameFromProperties(String url, ClassLoader classLoader) throws JAXBException {
+        logger.fine("Searching META-INF/services");
+
+        // search META-INF services next
+        BufferedReader r;
         try {
-            InputStream is;
-            if (classLoader != null) {
-                is = classLoader.getResourceAsStream(url);
+            final StringBuilder resource = new StringBuilder().append("META-INF/services/").append(jaxbContextFQCN);
+            final InputStream resourceStream =
+                    classLoader.getResourceAsStream(resource.toString());
+            
+            if (resourceStream != null) {
+                r = new BufferedReader(new InputStreamReader(resourceStream, "UTF-8"));
+                factoryClassName = r.readLine().trim();
+                r.close();
+                return newInstance(contextPath, factoryClassName, classLoader, properties);
             } else {
-                is = ClassLoader.getSystemResourceAsStream(url);
-            }
-            if (is != null) {
-                try {
-                    Properties props = new Properties();
-                    props.load(is);
-                    String className = props.getProperty(JAXB_CONTEXT_FACTORY);
-                    if (className == null) {
-                        throw new JAXBException("jaxb.properties file " + url + " should contain a " + JAXB_CONTEXT_FACTORY + " property");
-                    }
-                    return className.trim();
-                } finally {
-                    is.close();
-                }
-            } else {
-                return null;
+                logger.fine("Unable to load:" + resource.toString());
             }
+        } catch (UnsupportedEncodingException e) {
+            // should never happen
+            throw new JAXBException(e);
         } catch (IOException e) {
             throw new JAXBException(e);
         }
+
+        // else no provider found
+        logger.fine("Trying to create the platform default provider");
+        return newInstance(contextPath, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader, properties);
     }
 
-    private static String loadClassName(String url, ClassLoader classLoader) throws JAXBException {
-        try {
-            InputStream is;
-            if (classLoader != null) {
-                is = classLoader.getResourceAsStream(url);
+    // TODO: log each step in the look up process
+    static JAXBContext find( Class[] classes, Map properties ) throws JAXBException {
+
+        // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP?
+
+        final String jaxbContextFQCN = JAXBContext.class.getName();
+        String factoryClassName;
+
+        // search for jaxb.properties in the class loader of each class first
+        for (final Class c : classes) {
+            // this classloader is used only to load jaxb.properties, so doing this should be safe.
+            ClassLoader classLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+                public ClassLoader run() {
+                    return c.getClassLoader();
+                }
+            });
+            Package pkg = c.getPackage();
+            if(pkg==null)
+                continue;       // this is possible for primitives, arrays, and classes that are loaded by poorly implemented ClassLoaders
+            String packageName = pkg.getName().replace('.', '/');
+
+            // TODO: do we want to optimize away searching the same package?  org.Foo, org.Bar, com.Baz
+            //       classes from the same package might come from different class loades, so it might be a bad idea
+
+            // TODO: it's easier to look things up from the class
+            // c.getResourceAsStream("jaxb.properties");
+
+            // build the resource name and use the property loader code
+            String resourceName = packageName+"/jaxb.properties";
+            logger.fine("Trying to locate "+resourceName);
+            Properties props = loadJAXBProperties(classLoader, resourceName);
+            if (props == null) {
+                logger.fine("  not found");
             } else {
-                is = ClassLoader.getSystemResourceAsStream(url);
-            }
-            if (is != null) {
-                try {
-                    BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
-                    return r.readLine().trim();
-                } finally {
-                    is.close();
+                logger.fine("  found");
+                if (props.containsKey(JAXB_CONTEXT_FACTORY)) {
+                    // trim() seems redundant, but adding to satisfy customer complaint
+                    factoryClassName = props.getProperty(JAXB_CONTEXT_FACTORY).trim();
+                    return newInstance(classes, properties, factoryClassName);
+                } else {
+                    throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, JAXB_CONTEXT_FACTORY));
                 }
             }
-            return null;
+        }
+
+        // search for a system property second (javax.xml.bind.JAXBContext)
+        logger.fine("Checking system property "+jaxbContextFQCN);
+        factoryClassName = AccessController.doPrivileged(new GetPropertyAction(jaxbContextFQCN));
+        if(  factoryClassName != null ) {
+            logger.fine("  found "+factoryClassName);
+            return newInstance( classes, properties, factoryClassName );
+        }
+        logger.fine("  not found");
+
+        // search META-INF services next
+        logger.fine("Checking META-INF/services");
+        BufferedReader r;
+        try {
+            final String resource = new StringBuilder("META-INF/services/").append(jaxbContextFQCN).toString();
+            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+            URL resourceURL;
+            if(classLoader==null)
+                resourceURL = ClassLoader.getSystemResource(resource);
+            else
+                resourceURL = classLoader.getResource(resource);
+
+            if (resourceURL != null) {
+                logger.fine("Reading "+resourceURL);
+                r = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "UTF-8"));
+                factoryClassName = r.readLine().trim();
+                return newInstance(classes, properties, factoryClassName);
+            } else {
+                logger.fine("Unable to find: " + resource);
+            }
+        } catch (UnsupportedEncodingException e) {
+            // should never happen
+            throw new JAXBException(e);
         } catch (IOException e) {
             throw new JAXBException(e);
         }
+
+        // else no provider found
+        logger.fine("Trying to create the platform default provider");
+        return newInstance(classes, properties, PLATFORM_DEFAULT_FACTORY_CLASS);
     }
 
-    private static Class loadSpi(String className, ClassLoader classLoader) throws JAXBException {
-        Class spiClass;
+
+    private static Properties loadJAXBProperties( ClassLoader classLoader,
+                                                  String propFileName ) 
+        throws JAXBException {
+                                            
+        Properties props = null;
+                                                    
         try {
-            spiClass = org.apache.servicemix.specs.locator.OsgiLocator.locate(JAXBContext.class.getName());
-            if (spiClass != null) {
-                return spiClass;
-            }
-        } catch (Throwable t) {
+            URL url;
+            if(classLoader==null)
+                url = ClassLoader.getSystemResource(propFileName);
+            else
+                url = classLoader.getResource( propFileName );
+
+            if( url != null ) {
+                logger.fine("loading props from "+url);
+                props = new Properties();
+                InputStream is = url.openStream();
+                props.load( is );
+                is.close();
+            } 
+        } catch( IOException ioe ) {
+            logger.log(Level.FINE,"Unable to load "+propFileName,ioe);
+            throw new JAXBException( ioe.toString(), ioe );
         }
-        try {
-            if (classLoader != null) {
-                spiClass = classLoader.loadClass(className);
-            } else {
-                spiClass = Class.forName(className);
-            }
-        } catch (ClassNotFoundException e) {
-            throw new JAXBException("Provider " + className + " not found", e);
+        
+        return props;
+    }
+
+
+    /**
+     * Search the given ClassLoader for an instance of the specified class and
+     * return a string representation of the URL that points to the resource.
+     *
+     * @param clazz
+     *          The class to search for
+     * @param loader
+     *          The ClassLoader to search.  If this parameter is null, then the
+     *          system class loader will be searched
+     * @return
+     *          the URL for the class or null if it wasn't found
+     */
+    static URL which(Class clazz, ClassLoader loader) {
+
+        String classnameAsResource = clazz.getName().replace('.', '/') + ".class";
+
+        if(loader == null) {
+            loader = ClassLoader.getSystemClassLoader();
         }
-        return spiClass;
+
+        return loader.getResource(classnameAsResource);
     }
 
+    /**
+     * Get the URL for the Class from it's ClassLoader.
+     *
+     * Convenience method for {@link #which(Class, ClassLoader)}.
+     *
+     * Equivalent to calling: which(clazz, clazz.getClassLoader())
+     *
+     * @param clazz
+     *          The class to search for
+     * @return
+     *          the URL for the class or null if it wasn't found
+     */
+    static URL which(Class clazz) {
+        return which(clazz, clazz.getClassLoader());
+    }
 
-}
+    /**
+     * When JAXB is in J2SE, rt.jar has to have a JAXB implementation.
+     * However, rt.jar cannot have META-INF/services/javax.xml.bind.JAXBContext
+     * because if it has, it will take precedence over any file that applications have
+     * in their jar files.
+     *
+     * <p>
+     * When the user bundles his own JAXB implementation, we'd like to use it, and we
+     * want the platform default to be used only when there's no other JAXB provider.
+     *
+     * <p>
+     * For this reason, we have to hard-code the class name into the API.
+     */
+    private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.internal.bind.v2.ContextFactory";
+
+    /**
+     * Loads the class, provided that the calling thread has an access to the class being loaded.
+     */
+    private static Class safeLoadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
+       // using Osig locator to load the spi class 
+       try {
+           Class spiClass = org.apache.servicemix.specs.locator.OsgiLocator.locate(JAXBContext.class.getName());
+           if (spiClass != null) {
+               return spiClass;
+           }
+       } catch (Throwable t) {
+       }
+       logger.fine("Trying to load "+className);
+       try {
+          // make sure that the current thread has an access to the package of the given name.
+          SecurityManager s = System.getSecurityManager();
+          if (s != null) {
+              int i = className.lastIndexOf('.');
+              if (i != -1) {
+                  s.checkPackageAccess(className.substring(0,i));
+              }
+          }
+ 
+          if (classLoader == null) {
+              return Class.forName(className);
+          } else {
+              return classLoader.loadClass(className);
+          }
+       } catch (SecurityException se) {
+           // anyone can access the platform default factory class without permission
+           if (PLATFORM_DEFAULT_FACTORY_CLASS.equals(className)) {
+              return Class.forName(className);
+           }
+           throw se;
+       }
+    }
 
+}