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