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