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