Save This Page
Home » openjdk-7 » javax » xml » bind » [javadoc | source]
    1   /*
    2    * Copyright 2005-2006 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   
   26   package javax.xml.bind;
   27   
   28   import java.io.BufferedReader;
   29   import java.io.IOException;
   30   import java.io.InputStream;
   31   import java.io.InputStreamReader;
   32   import java.io.UnsupportedEncodingException;
   33   import java.lang.reflect.InvocationTargetException;
   34   import java.lang.reflect.Method;
   35   import java.net.URL;
   36   import java.util.Map;
   37   import java.util.Properties;
   38   import java.util.StringTokenizer;
   39   import java.util.logging.ConsoleHandler;
   40   import java.util.logging.Level;
   41   import java.util.logging.Logger;
   42   
   43   import static javax.xml.bind.JAXBContext.JAXB_CONTEXT_FACTORY;
   44   
   45   //import java.lang.reflect.InvocationTargetException;
   46   
   47   /**
   48    * This class is package private and therefore is not exposed as part of the
   49    * JAXB API.
   50    *
   51    * This code is designed to implement the JAXB 1.0 spec pluggability feature
   52    *
   53    * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
   54    * @see JAXBContext
   55    */
   56   class ContextFinder {
   57       private static final Logger logger;
   58       static {
   59           logger = Logger.getLogger("javax.xml.bind");
   60           try {
   61               if (System.getProperty("jaxb.debug", null) != null) {
   62                   // disconnect the logger from a bigger framework (if any)
   63                   // and take the matters into our own hands
   64                   logger.setUseParentHandlers(false);
   65                   logger.setLevel(Level.ALL);
   66                   ConsoleHandler handler = new ConsoleHandler();
   67                   handler.setLevel(Level.ALL);
   68                   logger.addHandler(handler);
   69               } else {
   70                   // don't change the setting of this logger
   71                   // to honor what other frameworks
   72                   // have done on configurations.
   73               }
   74           } catch(Throwable t) {
   75               // just to be extra safe. in particular System.getProperty may throw
   76               // SecurityException.
   77           }
   78       }
   79   
   80       /**
   81        * If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped,
   82        * throw the wrapped exception.
   83        */
   84       private static void handleInvocationTargetException(InvocationTargetException x) throws JAXBException {
   85           Throwable t = x.getTargetException();
   86           if( t != null ) {
   87               if( t instanceof JAXBException )
   88                   // one of our exceptions, just re-throw
   89                   throw (JAXBException)t;
   90               if( t instanceof RuntimeException )
   91                   // avoid wrapping exceptions unnecessarily
   92                   throw (RuntimeException)t;
   93               if( t instanceof Error )
   94                   throw (Error)t;
   95           }
   96       }
   97   
   98   
   99       /**
  100        * Determine if two types (JAXBContext in this case) will generate a ClassCastException.
  101        *
  102        * For example, (targetType)originalType
  103        *
  104        * @param originalType
  105        *          The Class object of the type being cast
  106        * @param targetType
  107        *          The Class object of the type that is being cast to
  108        * @throws JAXBException
  109        *          If the cast would fail
  110        */
  111       private static void handleClassCastException(Class originalType, Class targetType) throws JAXBException {
  112           final URL targetTypeURL = which(targetType);
  113   
  114           throw new JAXBException(Messages.format(Messages.ILLEGAL_CAST,
  115                   // we don't care where the impl class is, we want to know where JAXBContext lives in the impl
  116                   // class' ClassLoader
  117                   originalType.getClass().getClassLoader().getResource("javax/xml/bind/JAXBContext.class").toString(),
  118                   targetTypeURL.toString()));
  119       }
  120   
  121       /**
  122        * Create an instance of a class using the specified ClassLoader
  123        */
  124       static JAXBContext newInstance( String contextPath,
  125                                  String className,
  126                                  ClassLoader classLoader,
  127                                  Map properties )
  128           throws JAXBException
  129       {
  130           try {
  131               Class spiClass;
  132               if (classLoader == null) {
  133                   spiClass = Class.forName(className);
  134               } else {
  135                   spiClass = classLoader.loadClass(className);
  136               }
  137   
  138               /*
  139                * javax.xml.bind.context.factory points to a class which has a
  140                * static method called 'createContext' that
  141                * returns a javax.xml.JAXBContext.
  142                */
  143   
  144               Object context = null;
  145   
  146               // first check the method that takes Map as the third parameter.
  147               // this is added in 2.0.
  148               try {
  149                   Method m = spiClass.getMethod("createContext",String.class,ClassLoader.class,Map.class);
  150                   // any failure in invoking this method would be considered fatal
  151                   context = m.invoke(null,contextPath,classLoader,properties);
  152               } catch (NoSuchMethodException e) {
  153                   ; // it's not an error for the provider not to have this method.
  154               }
  155   
  156               if(context==null) {
  157                   // try the old method that doesn't take properties. compatible with 1.0.
  158                   // it is an error for an implementation not to have both forms of the createContext method.
  159                   Method m = spiClass.getMethod("createContext",String.class,ClassLoader.class);
  160                   // any failure in invoking this method would be considered fatal
  161                   context = m.invoke(null,contextPath,classLoader);
  162               }
  163   
  164               if(!(context instanceof JAXBContext)) {
  165                   // the cast would fail, so generate an exception with a nice message
  166                   handleClassCastException(context.getClass(), JAXBContext.class);
  167               }
  168               return (JAXBContext)context;
  169           } catch (ClassNotFoundException x) {
  170               throw new JAXBException(
  171                   Messages.format( Messages.PROVIDER_NOT_FOUND, className ),
  172                   x);
  173           } catch (InvocationTargetException x) {
  174               handleInvocationTargetException(x);
  175               // for other exceptions, wrap the internal target exception
  176               // with a JAXBException
  177               Throwable e = x;
  178               if(x.getTargetException()!=null)
  179                   e = x.getTargetException();
  180   
  181               throw new JAXBException( Messages.format( Messages.COULD_NOT_INSTANTIATE, className, e ), e );
  182           } catch (RuntimeException x) {
  183               // avoid wrapping RuntimeException to JAXBException,
  184               // because it indicates a bug in this code.
  185               throw x;
  186           } catch (Exception x) {
  187               // can't catch JAXBException because the method is hidden behind
  188               // reflection.  Root element collisions detected in the call to
  189               // createContext() are reported as JAXBExceptions - just re-throw it
  190               // some other type of exception - just wrap it
  191               throw new JAXBException(
  192                   Messages.format( Messages.COULD_NOT_INSTANTIATE, className, x ),
  193                   x);
  194           }
  195       }
  196   
  197   
  198       /**
  199        * Create an instance of a class using the specified ClassLoader
  200        */
  201       static JAXBContext newInstance(
  202                                 Class[] classes,
  203                                 Map properties,
  204                                 String className) throws JAXBException {
  205           ClassLoader cl = Thread.currentThread().getContextClassLoader();
  206           Class spi;
  207           try {
  208               logger.fine("Trying to load "+className);
  209               if (cl != null)
  210                   spi = cl.loadClass(className);
  211               else
  212                   spi = Class.forName(className);
  213           } catch (ClassNotFoundException e) {
  214               throw new JAXBException(e);
  215           }
  216   
  217           if(logger.isLoggable(Level.FINE)) {
  218               // extra check to avoid costly which operation if not logged
  219               logger.fine("loaded "+className+" from "+which(spi));
  220           }
  221   
  222           Method m;
  223           try {
  224               m = spi.getMethod("createContext", Class[].class, Map.class);
  225           } catch (NoSuchMethodException e) {
  226               throw new JAXBException(e);
  227           }
  228           try {
  229               Object context = m.invoke(null, classes, properties);
  230               if(!(context instanceof JAXBContext)) {
  231                   // the cast would fail, so generate an exception with a nice message
  232                   handleClassCastException(context.getClass(), JAXBContext.class);
  233               }
  234               return (JAXBContext)context;
  235           } catch (IllegalAccessException e) {
  236               throw new JAXBException(e);
  237           } catch (InvocationTargetException e) {
  238               handleInvocationTargetException(e);
  239   
  240               Throwable x = e;
  241               if (e.getTargetException() != null)
  242                   x = e.getTargetException();
  243   
  244               throw new JAXBException(x);
  245           }
  246       }
  247   
  248   
  249       static JAXBContext find(String factoryId, String contextPath, ClassLoader classLoader, Map properties ) throws JAXBException {
  250   
  251           // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP?
  252   
  253           final String jaxbContextFQCN = JAXBContext.class.getName();
  254   
  255           // search context path for jaxb.properties first
  256           StringBuilder propFileName;
  257           StringTokenizer packages = new StringTokenizer( contextPath, ":" );
  258           String factoryClassName;
  259   
  260           if(!packages.hasMoreTokens())
  261               // no context is specified
  262               throw new JAXBException(Messages.format(Messages.NO_PACKAGE_IN_CONTEXTPATH));
  263   
  264   
  265           logger.fine("Searching jaxb.properties");
  266   
  267           while( packages.hasMoreTokens() ) {
  268               String packageName = packages.nextToken(":").replace('.','/');
  269               // com.acme.foo - > com/acme/foo/jaxb.properties
  270                propFileName = new StringBuilder().append(packageName).append("/jaxb.properties");
  271   
  272               Properties props = loadJAXBProperties( classLoader, propFileName.toString() );
  273               if (props == null) {
  274                   continue;
  275               } else {
  276                   if (props.containsKey(factoryId)) {
  277                       factoryClassName = props.getProperty(factoryId);
  278                       return newInstance( contextPath, factoryClassName, classLoader, properties );
  279                   } else {
  280                       throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, factoryId));
  281                   }
  282               }
  283           }
  284   
  285           logger.fine("Searching the system property");
  286   
  287           // search for a system property second (javax.xml.bind.JAXBContext)
  288           factoryClassName = System.getProperty(jaxbContextFQCN, null);
  289           if(  factoryClassName != null ) {
  290               return newInstance( contextPath, factoryClassName, classLoader, properties );
  291           }
  292   
  293           logger.fine("Searching META-INF/services");
  294   
  295           // search META-INF services next
  296           BufferedReader r;
  297           try {
  298               final StringBuilder resource = new StringBuilder().append("META-INF/services/").append(jaxbContextFQCN);
  299               final InputStream resourceStream =
  300                       classLoader.getResourceAsStream(resource.toString());
  301   
  302               if (resourceStream != null) {
  303                   r = new BufferedReader(new InputStreamReader(resourceStream, "UTF-8"));
  304                   factoryClassName = r.readLine().trim();
  305                   return newInstance(contextPath, factoryClassName, classLoader, properties);
  306               } else {
  307                   logger.fine("Unable to load:" + resource.toString());
  308               }
  309           } catch (UnsupportedEncodingException e) {
  310               // should never happen
  311               throw new JAXBException(e);
  312           } catch (IOException e) {
  313               throw new JAXBException(e);
  314           }
  315   
  316           // else no provider found
  317           logger.fine("Trying to create the platform default provider");
  318           return newInstance(contextPath, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader, properties);
  319       }
  320   
  321       // TODO: log each step in the look up process
  322       static JAXBContext find( Class[] classes, Map properties ) throws JAXBException {
  323   
  324           // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP?
  325   
  326           final String jaxbContextFQCN = JAXBContext.class.getName();
  327           String factoryClassName;
  328   
  329           // search for jaxb.properties in the class loader of each class first
  330           for (Class c : classes) {
  331               ClassLoader classLoader = c.getClassLoader();
  332               Package pkg = c.getPackage();
  333               if(pkg==null)
  334                   continue;       // this is possible for primitives, arrays, and classes that are loaded by poorly implemented ClassLoaders
  335               String packageName = pkg.getName().replace('.', '/');
  336   
  337               // TODO: do we want to optimize away searching the same package?  org.Foo, org.Bar, com.Baz
  338               //       classes from the same package might come from different class loades, so it might be a bad idea
  339   
  340               // TODO: it's easier to look things up from the class
  341               // c.getResourceAsStream("jaxb.properties");
  342   
  343               // build the resource name and use the property loader code
  344               String resourceName = packageName+"/jaxb.properties";
  345               logger.fine("Trying to locate "+resourceName);
  346               Properties props = loadJAXBProperties(classLoader, resourceName);
  347               if (props == null) {
  348                   logger.fine("  not found");
  349                   continue;
  350               } else {
  351                   logger.fine("  found");
  352                   if (props.containsKey(JAXB_CONTEXT_FACTORY)) {
  353                       // trim() seems redundant, but adding to satisfy customer complaint
  354                       factoryClassName = props.getProperty(JAXB_CONTEXT_FACTORY).trim();
  355                       return newInstance(classes, properties, factoryClassName);
  356                   } else {
  357                       throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, JAXB_CONTEXT_FACTORY));
  358                   }
  359               }
  360           }
  361   
  362           // search for a system property second (javax.xml.bind.JAXBContext)
  363           logger.fine("Checking system property "+jaxbContextFQCN);
  364           factoryClassName = System.getProperty(jaxbContextFQCN, null);
  365           if(  factoryClassName != null ) {
  366               logger.fine("  found "+factoryClassName);
  367               return newInstance( classes, properties, factoryClassName );
  368           }
  369           logger.fine("  not found");
  370   
  371           // search META-INF services next
  372           logger.fine("Checking META-INF/services");
  373           BufferedReader r;
  374           try {
  375               final String resource = new StringBuilder("META-INF/services/").append(jaxbContextFQCN).toString();
  376               ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  377               URL resourceURL;
  378               if(classLoader==null)
  379                   resourceURL = ClassLoader.getSystemResource(resource);
  380               else
  381                   resourceURL = classLoader.getResource(resource);
  382   
  383               if (resourceURL != null) {
  384                   logger.fine("Reading "+resourceURL);
  385                   r = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "UTF-8"));
  386                   factoryClassName = r.readLine().trim();
  387                   return newInstance(classes, properties, factoryClassName);
  388               } else {
  389                   logger.fine("Unable to find: " + resource);
  390               }
  391           } catch (UnsupportedEncodingException e) {
  392               // should never happen
  393               throw new JAXBException(e);
  394           } catch (IOException e) {
  395               throw new JAXBException(e);
  396           }
  397   
  398           // else no provider found
  399           logger.fine("Trying to create the platform default provider");
  400           return newInstance(classes, properties, PLATFORM_DEFAULT_FACTORY_CLASS);
  401       }
  402   
  403   
  404       private static Properties loadJAXBProperties( ClassLoader classLoader,
  405                                                     String propFileName )
  406           throws JAXBException {
  407   
  408           Properties props = null;
  409   
  410           try {
  411               URL url;
  412               if(classLoader==null)
  413                   url = ClassLoader.getSystemResource(propFileName);
  414               else
  415                   url = classLoader.getResource( propFileName );
  416   
  417               if( url != null ) {
  418                   logger.fine("loading props from "+url);
  419                   props = new Properties();
  420                   InputStream is = url.openStream();
  421                   props.load( is );
  422                   is.close();
  423               }
  424           } catch( IOException ioe ) {
  425               logger.log(Level.FINE,"Unable to load "+propFileName,ioe);
  426               throw new JAXBException( ioe.toString(), ioe );
  427           }
  428   
  429           return props;
  430       }
  431   
  432   
  433       /**
  434        * Search the given ClassLoader for an instance of the specified class and
  435        * return a string representation of the URL that points to the resource.
  436        *
  437        * @param clazz
  438        *          The class to search for
  439        * @param loader
  440        *          The ClassLoader to search.  If this parameter is null, then the
  441        *          system class loader will be searched
  442        * @return
  443        *          the URL for the class or null if it wasn't found
  444        */
  445       static URL which(Class clazz, ClassLoader loader) {
  446   
  447           String classnameAsResource = clazz.getName().replace('.', '/') + ".class";
  448   
  449           if(loader == null) {
  450               loader = ClassLoader.getSystemClassLoader();
  451           }
  452   
  453           return loader.getResource(classnameAsResource);
  454       }
  455   
  456       /**
  457        * Get the URL for the Class from it's ClassLoader.
  458        *
  459        * Convenience method for {@link #which(Class, ClassLoader)}.
  460        *
  461        * Equivalent to calling: which(clazz, clazz.getClassLoader())
  462        *
  463        * @param clazz
  464        *          The class to search for
  465        * @return
  466        *          the URL for the class or null if it wasn't found
  467        */
  468       static URL which(Class clazz) {
  469           return which(clazz, clazz.getClassLoader());
  470       }
  471   
  472       /**
  473        * When JAXB is in J2SE, rt.jar has to have a JAXB implementation.
  474        * However, rt.jar cannot have META-INF/services/javax.xml.bind.JAXBContext
  475        * because if it has, it will take precedence over any file that applications have
  476        * in their jar files.
  477        *
  478        * <p>
  479        * When the user bundles his own JAXB implementation, we'd like to use it, and we
  480        * want the platform default to be used only when there's no other JAXB provider.
  481        *
  482        * <p>
  483        * For this reason, we have to hard-code the class name into the API.
  484        */
  485       private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.internal.bind.v2.ContextFactory";
  486   }

Save This Page
Home » openjdk-7 » javax » xml » bind » [javadoc | source]