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