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