1 /*
   2  * Copyright (c) 2003, 2015, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * 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.security.AccessController;
  37 import java.util.Map;
  38 import java.util.Properties;
  39 import java.util.StringTokenizer;
  40 import java.util.logging.ConsoleHandler;
  41 import java.util.logging.Level;
  42 import java.util.logging.Logger;
  43 
  44 
  45 /**
  46  * This class is package private and therefore is not exposed as part of the
  47  * JAXB API.
  48  *
  49  * This code is designed to implement the JAXB 1.0 spec pluggability feature
  50  *
  51  * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
  52  * @see JAXBContext
  53  */
  54 class ContextFinder {
  55 
  56     /**
  57      * When JAXB is in J2SE, rt.jar has to have a JAXB implementation.
  58      * However, rt.jar cannot have META-INF/services/javax.xml.bind.JAXBContext
  59      * because if it has, it will take precedence over any file that applications have
  60      * in their jar files.
  61      *
  62      * <p>
  63      * When the user bundles his own JAXB implementation, we'd like to use it, and we
  64      * want the platform default to be used only when there's no other JAXB provider.
  65      *
  66      * <p>
  67      * For this reason, we have to hard-code the class name into the API.
  68      */
  69     private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.internal.bind.v2.ContextFactory";
  70 
  71     private static final Logger logger;
  72 
  73     static {
  74         logger = Logger.getLogger("javax.xml.bind");
  75         try {
  76             if (AccessController.doPrivileged(new GetPropertyAction("jaxb.debug")) != null) {
  77                 // disconnect the logger from a bigger framework (if any)
  78                 // and take the matters into our own hands
  79                 logger.setUseParentHandlers(false);
  80                 logger.setLevel(Level.ALL);
  81                 ConsoleHandler handler = new ConsoleHandler();
  82                 handler.setLevel(Level.ALL);
  83                 logger.addHandler(handler);
  84             } else {
  85                 // don't change the setting of this logger
  86                 // to honor what other frameworks
  87                 // have done on configurations.
  88             }
  89         } catch (Throwable t) {
  90             // just to be extra safe. in particular System.getProperty may throw
  91             // SecurityException.
  92         }
  93     }
  94 
  95     /**
  96      * If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped,
  97      * throw the wrapped exception.
  98      */
  99     private static void handleInvocationTargetException(InvocationTargetException x) throws JAXBException {
 100         Throwable t = x.getTargetException();
 101         if (t != null) {
 102             if (t instanceof JAXBException)
 103                 // one of our exceptions, just re-throw
 104                 throw (JAXBException) t;
 105             if (t instanceof RuntimeException)
 106                 // avoid wrapping exceptions unnecessarily
 107                 throw (RuntimeException) t;
 108             if (t instanceof Error)
 109                 throw (Error) t;
 110         }
 111     }
 112 
 113 
 114     /**
 115      * Determine if two types (JAXBContext in this case) will generate a ClassCastException.
 116      *
 117      * For example, (targetType)originalType
 118      *
 119      * @param originalType
 120      *          The Class object of the type being cast
 121      * @param targetType
 122      *          The Class object of the type that is being cast to
 123      * @return JAXBException to be thrown.
 124      */
 125     private static JAXBException handleClassCastException(Class originalType, Class targetType) {
 126         final URL targetTypeURL = which(targetType);
 127 
 128         return new JAXBException(Messages.format(Messages.ILLEGAL_CAST,
 129                 // we don't care where the impl class is, we want to know where JAXBContext lives in the impl
 130                 // class' ClassLoader
 131                 getClassClassLoader(originalType).getResource("javax/xml/bind/JAXBContext.class"),
 132                 targetTypeURL));
 133     }
 134 
 135     /**
 136      * Create an instance of a class using the specified ClassLoader
 137      */
 138     static JAXBContext newInstance(String contextPath,
 139                                    String className,
 140                                    ClassLoader classLoader,
 141                                    Map properties) throws JAXBException {
 142 
 143         try {
 144             Class spFactory = ServiceLoaderUtil.safeLoadClass(className, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader);
 145             return newInstance(contextPath, spFactory, classLoader, properties);
 146         } catch (ClassNotFoundException x) {
 147             throw new JAXBException(Messages.format(Messages.PROVIDER_NOT_FOUND, className), x);
 148 
 149         } catch (RuntimeException x) {
 150             // avoid wrapping RuntimeException to JAXBException,
 151             // because it indicates a bug in this code.
 152             throw x;
 153         } catch (Exception x) {
 154             // can't catch JAXBException because the method is hidden behind
 155             // reflection.  Root element collisions detected in the call to
 156             // createContext() are reported as JAXBExceptions - just re-throw it
 157             // some other type of exception - just wrap it
 158             throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, className, x), x);
 159         }
 160     }
 161 
 162     static JAXBContext newInstance(String contextPath, Class spFactory, ClassLoader classLoader, Map properties) throws JAXBException {
 163 
 164         try {
 165             /*
 166              * javax.xml.bind.context.factory points to a class which has a
 167              * static method called 'createContext' that
 168              * returns a javax.xml.JAXBContext.
 169              */
 170 
 171             Object context = null;
 172 
 173             // first check the method that takes Map as the third parameter.
 174             // this is added in 2.0.
 175             try {
 176                 Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class, Map.class);
 177                 // any failure in invoking this method would be considered fatal
 178                 context = m.invoke(null, contextPath, classLoader, properties);
 179             } catch (NoSuchMethodException e) {
 180                 // it's not an error for the provider not to have this method.
 181             }
 182 
 183             if (context == null) {
 184                 // try the old method that doesn't take properties. compatible with 1.0.
 185                 // it is an error for an implementation not to have both forms of the createContext method.
 186                 Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class);
 187                 // any failure in invoking this method would be considered fatal
 188                 context = m.invoke(null, contextPath, classLoader);
 189             }
 190 
 191             if (!(context instanceof JAXBContext)) {
 192                 // the cast would fail, so generate an exception with a nice message
 193                 throw handleClassCastException(context.getClass(), JAXBContext.class);
 194             }
 195             return (JAXBContext) context;
 196         } catch (InvocationTargetException x) {
 197             handleInvocationTargetException(x);
 198             // for other exceptions, wrap the internal target exception
 199             // with a JAXBException
 200             Throwable e = x;
 201             if (x.getTargetException() != null)
 202                 e = x.getTargetException();
 203 
 204             throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, e), e);
 205         } catch (RuntimeException x) {
 206             // avoid wrapping RuntimeException to JAXBException,
 207             // because it indicates a bug in this code.
 208             throw x;
 209         } catch (Exception x) {
 210             // can't catch JAXBException because the method is hidden behind
 211             // reflection.  Root element collisions detected in the call to
 212             // createContext() are reported as JAXBExceptions - just re-throw it
 213             // some other type of exception - just wrap it
 214             throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, x), x);
 215         }
 216     }
 217 
 218     /**
 219      * Create an instance of a class using the thread context ClassLoader
 220      */
 221     static JAXBContext newInstance(Class[] classes, Map properties, String className) throws JAXBException {
 222 
 223         Class spi;
 224         try {
 225             spi = ServiceLoaderUtil.safeLoadClass(className, PLATFORM_DEFAULT_FACTORY_CLASS, getContextClassLoader());
 226         } catch (ClassNotFoundException e) {
 227             throw new JAXBException(e);
 228         }
 229 
 230         if (logger.isLoggable(Level.FINE)) {
 231             // extra check to avoid costly which operation if not logged
 232             logger.log(Level.FINE, "loaded {0} from {1}", new Object[]{className, which(spi)});
 233         }
 234 
 235         return newInstance(classes, properties, spi);
 236     }
 237 
 238     static JAXBContext newInstance(Class[] classes,
 239                                    Map properties,
 240                                    Class spFactory) throws JAXBException {
 241         try {
 242             Method m = spFactory.getMethod("createContext", Class[].class, Map.class);
 243             Object context = m.invoke(null, classes, properties);
 244             if (!(context instanceof JAXBContext)) {
 245                 // the cast would fail, so generate an exception with a nice message
 246                 throw handleClassCastException(context.getClass(), JAXBContext.class);
 247             }
 248             return (JAXBContext) context;
 249         } catch (NoSuchMethodException e) {
 250             throw new JAXBException(e);
 251         } catch (IllegalAccessException e) {
 252             throw new JAXBException(e);
 253         } catch (InvocationTargetException e) {
 254             handleInvocationTargetException(e);
 255 
 256             Throwable x = e;
 257             if (e.getTargetException() != null)
 258                 x = e.getTargetException();
 259 
 260             throw new JAXBException(x);
 261         }
 262     }
 263 
 264     static JAXBContext find(String factoryId, String contextPath, ClassLoader classLoader, Map properties) throws JAXBException {
 265 
 266         // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP?
 267 
 268         StringTokenizer packages = new StringTokenizer(contextPath, ":");
 269         if (!packages.hasMoreTokens()) {
 270             // no context is specified
 271             throw new JAXBException(Messages.format(Messages.NO_PACKAGE_IN_CONTEXTPATH));
 272         }
 273 
 274         // search for jaxb.properties in the class loader of each class first
 275         logger.fine("Searching jaxb.properties");
 276         while (packages.hasMoreTokens()) {
 277             // com.acme.foo - > com/acme/foo/jaxb.properties
 278             String className = classNameFromPackageProperties(factoryId, classLoader, packages.nextToken(":").replace('.', '/'));
 279             if (className != null) return newInstance(contextPath, className, classLoader, properties);
 280         }
 281 
 282         String factoryName = classNameFromSystemProperties();
 283         if (factoryName != null) return newInstance(contextPath, factoryName, classLoader, properties);
 284 
 285         Class ctxFactory = (Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader("javax.xml.bind.JAXBContext", logger);
 286         if (ctxFactory != null) {
 287             return newInstance(contextPath, ctxFactory, classLoader, properties);
 288         }
 289 
 290         // TODO: SPEC change required! This is supposed to be!
 291         // JAXBContext obj = firstByServiceLoader(JAXBContext.class, EXCEPTION_HANDLER);
 292         // if (obj != null) return obj;
 293 
 294         // TODO: Deprecated - SPEC change required!
 295         factoryName = firstByServiceLoaderDeprecated(JAXBContext.class, classLoader);
 296         if (factoryName != null) return newInstance(contextPath, factoryName, classLoader, properties);
 297 
 298         // else no provider found
 299         logger.fine("Trying to create the platform default provider");
 300         return newInstance(contextPath, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader, properties);
 301     }
 302 
 303     static JAXBContext find(Class[] classes, Map properties) throws JAXBException {
 304 
 305         // search for jaxb.properties in the class loader of each class first
 306         logger.fine("Searching jaxb.properties");
 307         for (final Class c : classes) {
 308             // this classloader is used only to load jaxb.properties, so doing this should be safe.
 309             if (c.getPackage() == null) continue;       // this is possible for primitives, arrays, and classes that are loaded by poorly implemented ClassLoaders
 310 
 311             // TODO: do we want to optimize away searching the same package?  org.Foo, org.Bar, com.Baz
 312             // classes from the same package might come from different class loades, so it might be a bad idea
 313             // TODO: it's easier to look things up from the class
 314             // c.getResourceAsStream("jaxb.properties");
 315 
 316             String className = classNameFromPackageProperties(JAXBContext.JAXB_CONTEXT_FACTORY, getClassClassLoader(c), c.getPackage().getName().replace('.', '/'));
 317             if (className != null) return newInstance(classes, properties, className);
 318         }
 319 
 320         String factoryName = classNameFromSystemProperties();
 321         if (factoryName != null) return newInstance(classes, properties, factoryName);
 322 
 323         Class ctxFactoryClass = (Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader("javax.xml.bind.JAXBContext", logger);
 324         if (ctxFactoryClass != null) {
 325             return newInstance(classes, properties, ctxFactoryClass);
 326         }
 327 
 328         // TODO: to be removed - deprecated!!! Requires SPEC change!!!
 329         String className = firstByServiceLoaderDeprecated(JAXBContext.class, getContextClassLoader());
 330         if (className != null) return newInstance(classes, properties, className);
 331 
 332         //    // TODO: supposed to be:
 333         //    obj = firstByServiceLoader(JAXBContext.class, EXCEPTION_HANDLER);
 334         //    if (obj != null) return obj;
 335 
 336         // else no provider found
 337         logger.fine("Trying to create the platform default provider");
 338         return newInstance(classes, properties, PLATFORM_DEFAULT_FACTORY_CLASS);
 339     }
 340 
 341 
 342     private static String classNameFromPackageProperties(String factoryId, ClassLoader classLoader, String packageName) throws JAXBException {
 343         String resourceName = packageName + "/jaxb.properties";
 344         logger.log(Level.FINE, "Trying to locate {0}", resourceName);
 345         Properties props = loadJAXBProperties(classLoader, resourceName);
 346         if (props != null) {
 347             if (props.containsKey(factoryId)) {
 348                 return props.getProperty(factoryId);
 349             } else {
 350                 throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, factoryId));
 351             }
 352         }
 353         return null;
 354     }
 355 
 356     private static String classNameFromSystemProperties() throws JAXBException {
 357         logger.log(Level.FINE, "Checking system property {0}", JAXBContext.JAXB_CONTEXT_FACTORY);
 358         // search for a system property second (javax.xml.bind.JAXBContext)
 359         String factoryClassName = AccessController.doPrivileged(new GetPropertyAction(JAXBContext.JAXB_CONTEXT_FACTORY));
 360         if (factoryClassName != null) {
 361             logger.log(Level.FINE, "  found {0}", factoryClassName);
 362             return factoryClassName;
 363         } else { // leave this here to assure compatibility
 364             logger.fine("  not found");
 365             logger.log(Level.FINE, "Checking system property {0}", JAXBContext.class.getName());
 366             factoryClassName = AccessController.doPrivileged(new GetPropertyAction(JAXBContext.class.getName()));
 367             if (factoryClassName != null) {
 368                 logger.log(Level.FINE, "  found {0}", factoryClassName);
 369                 return factoryClassName;
 370             } else {
 371                 logger.fine("  not found");
 372             }
 373         }
 374         return null;
 375     }
 376 
 377     private static Properties loadJAXBProperties(ClassLoader classLoader, String propFileName) throws JAXBException {
 378 
 379         Properties props = null;
 380         try {
 381             URL url;
 382             if (classLoader == null)
 383                 url = ClassLoader.getSystemResource(propFileName);
 384             else
 385                 url = classLoader.getResource(propFileName);
 386 
 387             if (url != null) {
 388                 logger.log(Level.FINE, "loading props from {0}", url);
 389                 props = new Properties();
 390                 InputStream is = url.openStream();
 391                 props.load(is);
 392                 is.close();
 393             }
 394         } catch (IOException ioe) {
 395             logger.log(Level.FINE, "Unable to load " + propFileName, ioe);
 396             throw new JAXBException(ioe.toString(), ioe);
 397         }
 398 
 399         return props;
 400     }
 401 
 402 
 403     /**
 404      * Search the given ClassLoader for an instance of the specified class and
 405      * return a string representation of the URL that points to the resource.
 406      *
 407      * @param clazz
 408      *          The class to search for
 409      * @param loader
 410      *          The ClassLoader to search.  If this parameter is null, then the
 411      *          system class loader will be searched
 412      * @return
 413      *          the URL for the class or null if it wasn't found
 414      */
 415     static URL which(Class clazz, ClassLoader loader) {
 416 
 417         String classnameAsResource = clazz.getName().replace('.', '/') + ".class";
 418 
 419         if (loader == null) {
 420             loader = getSystemClassLoader();
 421         }
 422 
 423         return loader.getResource(classnameAsResource);
 424     }
 425 
 426     /**
 427      * Get the URL for the Class from it's ClassLoader.
 428      *
 429      * Convenience method for {@link #which(Class, ClassLoader)}.
 430      *
 431      * Equivalent to calling: which(clazz, clazz.getClassLoader())
 432      *
 433      * @param clazz
 434      *          The class to search for
 435      * @return
 436      *          the URL for the class or null if it wasn't found
 437      */
 438     static URL which(Class clazz) {
 439         return which(clazz, getClassClassLoader(clazz));
 440     }
 441 
 442     @SuppressWarnings("unchecked")
 443     private static ClassLoader getContextClassLoader() {
 444         if (System.getSecurityManager() == null) {
 445             return Thread.currentThread().getContextClassLoader();
 446         } else {
 447             return (ClassLoader) java.security.AccessController.doPrivileged(
 448                     new java.security.PrivilegedAction() {
 449                         public java.lang.Object run() {
 450                             return Thread.currentThread().getContextClassLoader();
 451                         }
 452                     });
 453         }
 454     }
 455 
 456     @SuppressWarnings("unchecked")
 457     private static ClassLoader getClassClassLoader(final Class c) {
 458         if (System.getSecurityManager() == null) {
 459             return c.getClassLoader();
 460         } else {
 461             return (ClassLoader) java.security.AccessController.doPrivileged(
 462                     new java.security.PrivilegedAction() {
 463                         public java.lang.Object run() {
 464                             return c.getClassLoader();
 465                         }
 466                     });
 467         }
 468     }
 469 
 470     private static ClassLoader getSystemClassLoader() {
 471         if (System.getSecurityManager() == null) {
 472             return ClassLoader.getSystemClassLoader();
 473         } else {
 474             return (ClassLoader) java.security.AccessController.doPrivileged(
 475                     new java.security.PrivilegedAction() {
 476                         public java.lang.Object run() {
 477                             return ClassLoader.getSystemClassLoader();
 478                         }
 479                     });
 480         }
 481     }
 482 
 483     // TODO: to be removed - SPEC change required
 484     //    ServiceLoaderUtil.firstByServiceLoaderDeprecated should be used instead.
 485     @Deprecated
 486     static String firstByServiceLoaderDeprecated(Class spiClass, ClassLoader classLoader) throws JAXBException {
 487         final String jaxbContextFQCN = spiClass.getName();
 488 
 489         logger.fine("Searching META-INF/services");
 490 
 491         // search META-INF services next
 492         BufferedReader r = null;
 493         final String resource = new StringBuilder().append("META-INF/services/").append(jaxbContextFQCN).toString();
 494         try {
 495             final InputStream resourceStream =
 496                     (classLoader == null) ?
 497                             ClassLoader.getSystemResourceAsStream(resource) :
 498                             classLoader.getResourceAsStream(resource);
 499 
 500             if (resourceStream != null) {
 501                 r = new BufferedReader(new InputStreamReader(resourceStream, "UTF-8"));
 502                 String factoryClassName = r.readLine();
 503                 if (factoryClassName != null) {
 504                     factoryClassName = factoryClassName.trim();
 505                 }
 506                 r.close();
 507                 logger.log(Level.FINE, "Configured factorty class:{0}", factoryClassName);
 508                 return factoryClassName;
 509             } else {
 510                 logger.log(Level.FINE, "Unable to load:{0}", resource);
 511                 return null;
 512             }
 513         } catch (UnsupportedEncodingException e) {
 514             // should never happen
 515             throw new JAXBException(e);
 516         } catch (IOException e) {
 517             throw new JAXBException(e);
 518         } finally {
 519             try {
 520                 if (r != null) {
 521                     r.close();
 522                 }
 523             } catch (IOException ex) {
 524                 logger.log(Level.SEVERE, "Unable to close resource: " + resource, ex);
 525             }
 526         }
 527     }
 528 
 529 }