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