1 /*
   2  * Copyright (c) 2004, 2013, 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.xpath;
  27 
  28 import java.io.File;
  29 import java.lang.reflect.Method;
  30 import java.lang.reflect.Modifier;
  31 import java.net.URL;
  32 import java.security.AccessControlContext;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.util.Properties;
  36 import java.util.ServiceConfigurationError;
  37 import java.util.ServiceLoader;
  38 
  39 /**
  40  * Implementation of {@link XPathFactory#newInstance(String)}.
  41  *
  42  * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
  43  * @since 1.5
  44  */
  45 class XPathFactoryFinder  {
  46     private static final String DEFAULT_PACKAGE = "com.sun.org.apache.xpath.internal";
  47 
  48     private static final SecuritySupport ss = new SecuritySupport() ;
  49     /** debug support code. */
  50     private static boolean debug = false;
  51     static {
  52         // Use try/catch block to support applets
  53         try {
  54             debug = ss.getSystemProperty("jaxp.debug") != null;
  55         } catch (Exception unused) {
  56             debug = false;
  57         }
  58     }
  59 
  60     /**
  61      * <p>Cache properties for performance.</p>
  62      */
  63     private static final Properties cacheProps = new Properties();
  64 
  65     /**
  66      * <p>First time requires initialization overhead.</p>
  67      */
  68     private volatile static boolean firstTime = true;
  69 
  70     /**
  71      * <p>Conditional debug printing.</p>
  72      *
  73      * @param msg to print
  74      */
  75     private static void debugPrintln(String msg) {
  76         if (debug) {
  77             System.err.println("JAXP: " + msg);
  78         }
  79     }
  80 
  81     /**
  82      * <p><code>ClassLoader</code> to use to find <code>XPathFactory</code>.</p>
  83      */
  84     private final ClassLoader classLoader;
  85 
  86     /**
  87      * <p>Constructor that specifies <code>ClassLoader</code> to use
  88      * to find <code>XPathFactory</code>.</p>
  89      *
  90      * @param loader
  91      *      to be used to load resource and {@link XPathFactory}
  92      *      implementations during the resolution process.
  93      *      If this parameter is null, the default system class loader
  94      *      will be used.
  95      */
  96     public XPathFactoryFinder(ClassLoader loader) {
  97         this.classLoader = loader;
  98         if( debug ) {
  99             debugDisplayClassLoader();
 100         }
 101     }
 102 
 103     private void debugDisplayClassLoader() {
 104         try {
 105             if( classLoader == ss.getContextClassLoader() ) {
 106                 debugPrintln("using thread context class loader ("+classLoader+") for search");
 107                 return;
 108             }
 109         } catch( Throwable unused ) {
 110              // getContextClassLoader() undefined in JDK1.1
 111         }
 112 
 113         if( classLoader==ClassLoader.getSystemClassLoader() ) {
 114             debugPrintln("using system class loader ("+classLoader+") for search");
 115             return;
 116         }
 117 
 118         debugPrintln("using class loader ("+classLoader+") for search");
 119     }
 120 
 121     /**
 122      * <p>Creates a new {@link XPathFactory} object for the specified
 123      * object model.</p>
 124      *
 125      * @param uri
 126      *       Identifies the underlying object model.
 127      *
 128      * @return <code>null</code> if the callee fails to create one.
 129      *
 130      * @throws NullPointerException
 131      *      If the parameter is null.
 132      */
 133     public XPathFactory newFactory(String uri) throws XPathFactoryConfigurationException {
 134         if (uri == null) {
 135             throw new NullPointerException();
 136         }
 137         XPathFactory f = _newFactory(uri);
 138         if (f != null) {
 139             debugPrintln("factory '" + f.getClass().getName() + "' was found for " + uri);
 140         } else {
 141             debugPrintln("unable to find a factory for " + uri);
 142         }
 143         return f;
 144     }
 145 
 146     /**
 147      * <p>Lookup a {@link XPathFactory} for the given object model.</p>
 148      *
 149      * @param uri identifies the object model.
 150      *
 151      * @return {@link XPathFactory} for the given object model.
 152      */
 153     private XPathFactory _newFactory(String uri) throws XPathFactoryConfigurationException {
 154         XPathFactory xpathFactory = null;
 155 
 156         String propertyName = SERVICE_CLASS.getName() + ":" + uri;
 157 
 158         // system property look up
 159         try {
 160             debugPrintln("Looking up system property '"+propertyName+"'" );
 161             String r = ss.getSystemProperty(propertyName);
 162             if(r!=null) {
 163                 debugPrintln("The value is '"+r+"'");
 164                 xpathFactory = createInstance(r, true);
 165                 if (xpathFactory != null) {
 166                     return xpathFactory;
 167                 }
 168             } else
 169                 debugPrintln("The property is undefined.");
 170         } catch( Throwable t ) {
 171             if( debug ) {
 172                 debugPrintln("failed to look up system property '"+propertyName+"'" );
 173                 t.printStackTrace();
 174             }
 175         }
 176 
 177         String javah = ss.getSystemProperty( "java.home" );
 178         String configFile = javah + File.separator +
 179         "conf" + File.separator + "jaxp.properties";
 180 
 181         // try to read from $java.home/conf/jaxp.properties
 182         try {
 183             if(firstTime){
 184                 synchronized(cacheProps){
 185                     if(firstTime){
 186                         File f=new File( configFile );
 187                         firstTime = false;
 188                         if(ss.doesFileExist(f)){
 189                             debugPrintln("Read properties file " + f);
 190                             cacheProps.load(ss.getFileInputStream(f));
 191                         }
 192                     }
 193                 }
 194             }
 195             final String factoryClassName = cacheProps.getProperty(propertyName);
 196             debugPrintln("found " + factoryClassName + " in $java.home/conf/jaxp.properties");
 197 
 198             if (factoryClassName != null) {
 199                 xpathFactory = createInstance(factoryClassName, true);
 200                 if(xpathFactory != null){
 201                     return xpathFactory;
 202                 }
 203             }
 204         } catch (Exception ex) {
 205             if (debug) {
 206                 ex.printStackTrace();
 207             }
 208         }
 209 
 210         // Try with ServiceLoader
 211         assert xpathFactory == null;
 212         xpathFactory = findServiceProvider(uri);
 213 
 214         // The following assertion should always be true.
 215         // Uncomment it, recompile, and run with -ea in case of doubts:
 216         // assert xpathFactory == null || xpathFactory.isObjectModelSupported(uri);
 217 
 218         if (xpathFactory != null) {
 219             return xpathFactory;
 220         }
 221 
 222         // platform default
 223         if(uri.equals(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) {
 224             debugPrintln("attempting to use the platform default W3C DOM XPath lib");
 225             return createInstance("com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl", true);
 226         }
 227 
 228         debugPrintln("all things were tried, but none was found. bailing out.");
 229         return null;
 230     }
 231 
 232     /** <p>Create class using appropriate ClassLoader.</p>
 233      *
 234      * @param className Name of class to create.
 235      * @return Created class or <code>null</code>.
 236      */
 237     private Class<?> createClass(String className) {
 238         Class clazz;
 239         // make sure we have access to restricted packages
 240         boolean internal = false;
 241         if (System.getSecurityManager() != null) {
 242             if (className != null && className.startsWith(DEFAULT_PACKAGE)) {
 243                 internal = true;
 244             }
 245         }
 246 
 247         // use approprite ClassLoader
 248         try {
 249             if (classLoader != null && !internal) {
 250                     clazz = Class.forName(className, false, classLoader);
 251             } else {
 252                     clazz = Class.forName(className);
 253             }
 254         } catch (Throwable t) {
 255             if(debug) {
 256                 t.printStackTrace();
 257             }
 258             return null;
 259         }
 260 
 261         return clazz;
 262     }
 263 
 264     /**
 265      * <p>Creates an instance of the specified and returns it.</p>
 266      *
 267      * @param className
 268      *      fully qualified class name to be instantiated.
 269      *
 270      * @return null
 271      *      if it fails. Error messages will be printed by this method.
 272      */
 273     XPathFactory createInstance( String className )
 274             throws XPathFactoryConfigurationException
 275     {
 276         return createInstance( className, false );
 277     }
 278 
 279     XPathFactory createInstance( String className, boolean useServicesMechanism  )
 280             throws XPathFactoryConfigurationException
 281     {
 282         XPathFactory xPathFactory = null;
 283 
 284         debugPrintln("createInstance(" + className + ")");
 285 
 286         // get Class from className
 287         Class<?> clazz = createClass(className);
 288         if (clazz == null) {
 289             debugPrintln("failed to getClass(" + className + ")");
 290             return null;
 291         }
 292         debugPrintln("loaded " + className + " from " + which(clazz));
 293 
 294         // instantiate Class as a XPathFactory
 295         try {
 296             if (!useServicesMechanism) {
 297                 xPathFactory = newInstanceNoServiceLoader(clazz);
 298             }
 299             if (xPathFactory == null) {
 300                 xPathFactory = (XPathFactory) clazz.newInstance();
 301             }
 302         } catch (ClassCastException classCastException) {
 303                 debugPrintln("could not instantiate " + clazz.getName());
 304                 if (debug) {
 305                         classCastException.printStackTrace();
 306                 }
 307                 return null;
 308         } catch (IllegalAccessException illegalAccessException) {
 309                 debugPrintln("could not instantiate " + clazz.getName());
 310                 if (debug) {
 311                         illegalAccessException.printStackTrace();
 312                 }
 313                 return null;
 314         } catch (InstantiationException instantiationException) {
 315                 debugPrintln("could not instantiate " + clazz.getName());
 316                 if (debug) {
 317                         instantiationException.printStackTrace();
 318                 }
 319                 return null;
 320         }
 321 
 322         return xPathFactory;
 323     }
 324     /**
 325      * Try to construct using newXPathFactoryNoServiceLoader
 326      *   method if available.
 327      */
 328     private static XPathFactory newInstanceNoServiceLoader(
 329          Class<?> providerClass
 330     ) throws XPathFactoryConfigurationException {
 331         // Retain maximum compatibility if no security manager.
 332         if (System.getSecurityManager() == null) {
 333             return null;
 334         }
 335         try {
 336             Method creationMethod =
 337                     providerClass.getDeclaredMethod(
 338                         "newXPathFactoryNoServiceLoader"
 339                     );
 340             final int modifiers = creationMethod.getModifiers();
 341 
 342             // Do not call "newXPathFactoryNoServiceLoader" if it's
 343             // not public static.
 344             if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
 345                 return null;
 346             }
 347 
 348             // Only calls "newXPathFactoryNoServiceLoader" if it's
 349             // declared to return an instance of XPathFactory.
 350             final Class<?> returnType = creationMethod.getReturnType();
 351             if (SERVICE_CLASS.isAssignableFrom(returnType)) {
 352                 return SERVICE_CLASS.cast(creationMethod.invoke(null, (Object[])null));
 353             } else {
 354                 // Should not happen since
 355                 // XPathFactoryImpl.newXPathFactoryNoServiceLoader is
 356                 // declared to return XPathFactory.
 357                 throw new ClassCastException(returnType
 358                             + " cannot be cast to " + SERVICE_CLASS);
 359             }
 360         } catch (ClassCastException e) {
 361             throw new XPathFactoryConfigurationException(e);
 362         } catch (NoSuchMethodException exc) {
 363             return null;
 364         } catch (Exception exc) {
 365             return null;
 366         }
 367     }
 368 
 369     // Call isObjectModelSupportedBy with initial context.
 370     private boolean isObjectModelSupportedBy(final XPathFactory factory,
 371             final String objectModel,
 372             AccessControlContext acc) {
 373         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 374                     public Boolean run() {
 375                         return factory.isObjectModelSupported(objectModel);
 376                     }
 377                 }, acc);
 378     }
 379 
 380     /**
 381      * Finds a service provider subclass of XPathFactory that supports the
 382      * given object model using the ServiceLoader.
 383      *
 384      * @param objectModel URI of object model to support.
 385      * @return An XPathFactory supporting the specified object model, or null
 386      *         if none is found.
 387      * @throws XPathFactoryConfigurationException if a configuration error is found.
 388      */
 389     private XPathFactory findServiceProvider(final String objectModel)
 390             throws XPathFactoryConfigurationException {
 391 
 392         assert objectModel != null;
 393         // store current context.
 394         final AccessControlContext acc = AccessController.getContext();
 395         try {
 396             return AccessController.doPrivileged(new PrivilegedAction<XPathFactory>() {
 397                 public XPathFactory run() {
 398                     final ServiceLoader<XPathFactory> loader =
 399                             ServiceLoader.load(SERVICE_CLASS);
 400                     for (XPathFactory factory : loader) {
 401                         // restore initial context to call
 402                         // factory.isObjectModelSupportedBy
 403                         if (isObjectModelSupportedBy(factory, objectModel, acc)) {
 404                             return factory;
 405                         }
 406                     }
 407                     return null; // no factory found.
 408                 }
 409             });
 410         } catch (ServiceConfigurationError error) {
 411             throw new XPathFactoryConfigurationException(error);
 412         }
 413     }
 414 
 415     private static final Class<XPathFactory> SERVICE_CLASS = XPathFactory.class;
 416 
 417     private static String which( Class clazz ) {
 418         return which( clazz.getName(), clazz.getClassLoader() );
 419     }
 420 
 421     /**
 422      * <p>Search the specified classloader for the given classname.</p>
 423      *
 424      * @param classname the fully qualified name of the class to search for
 425      * @param loader the classloader to search
 426      *
 427      * @return the source location of the resource, or null if it wasn't found
 428      */
 429     private static String which(String classname, ClassLoader loader) {
 430 
 431         String classnameAsResource = classname.replace('.', '/') + ".class";
 432 
 433         if( loader==null )  loader = ClassLoader.getSystemClassLoader();
 434 
 435         //URL it = loader.getResource(classnameAsResource);
 436         URL it = ss.getResourceAsURL(loader, classnameAsResource);
 437         if (it != null) {
 438             return it.toString();
 439         } else {
 440             return null;
 441         }
 442     }
 443 }