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.InvocationTargetException;
  30 import java.lang.reflect.Method;
  31 import java.net.URL;
  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 
  38 /**
  39  * Implementation of {@link SchemaFactory#newInstance(String)}.
  40  *
  41  * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
  42  * @version $Revision: 1.8 $, $Date: 2010-11-01 04:36:13 $
  43  * @since 1.5
  44  */
  45 class SchemaFactoryFinder {
  46 
  47     private static final String DEFAULT_IMPL_NAME = "com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory";
  48     /**
  49      * debug support code.
  50      */
  51     private static boolean debug = false;
  52     /**
  53      * <p> Take care of restrictions imposed by java security model </p>
  54      */
  55     private static SecuritySupport ss = new SecuritySupport();
  56     /**
  57      * <p>Cache properties for performance.</p>
  58      */
  59     private static Properties cacheProps = new Properties();
  60     /**
  61      * <p>First time requires initialization overhead.</p>
  62      */
  63     private static volatile boolean firstTime = true;
  64 
  65     static {
  66         // Use try/catch block to support applets
  67         try {
  68             debug = ss.getSystemProperty("jaxp.debug") != null;
  69         } catch (Exception _) {
  70             debug = false;
  71         }
  72     }
  73 
  74     /**
  75      * <p>Conditional debug printing.</p>
  76      *
  77      * @param msg to print
  78      */
  79     private static void debugPrintln(String msg) {
  80         if (debug) {
  81             System.err.println("JAXP: " + msg);
  82         }
  83     }
  84     /**
  85      * <p><code>ClassLoader</code> to use to find
  86      * <code>SchemaFactory</code>.</p>
  87      */
  88     private final ClassLoader classLoader;
  89 
  90     /**
  91      * <p>Constructor that specifies
  92      * <code>ClassLoader</code> to use to find
  93      * <code>SchemaFactory</code>.</p>
  94      *
  95      * @param loader to be used to load resource, {@link SchemaFactory}, and
  96      * {@link SchemaFactoryLoader} implementations during the resolution
  97      * process. If this parameter is null, the default system class loader will
  98      * 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 _) {
 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 schema
 127      * language.</p>
 128      *
 129      * @param schemaLanguage See {@link SchemaFactory Schema Language} table
 130      * in <code>SchemaFactory</code> for the list of available schema languages.
 131      *
 132      * @return <code>null</code> if the callee fails to create one.
 133      *
 134      * @throws NullPointerException If the <code>schemaLanguage</code> parameter
 135      * is null.
 136      */
 137     public SchemaFactory newFactory(String schemaLanguage)
 138             throws FactoryConfigurationError {
 139         if (schemaLanguage == null) {
 140             throw new NullPointerException();
 141         }
 142         SchemaFactory f = _newFactory(schemaLanguage);
 143         if (f != null) {
 144             debugPrintln("factory '" + f.getClass().getName() + "' was found for " + schemaLanguage);
 145         } else {
 146             debugPrintln("unable to find a factory for " + schemaLanguage);
 147         }
 148         return f;
 149     }
 150 
 151     /**
 152      * <p>Lookup a
 153      * <code>SchemaFactory</code> for the given
 154      * <code>schemaLanguage</code>.</p>
 155      *
 156      * @param schemaLanguage Schema language to
 157      * lookup <code>SchemaFactory</code> for.
 158      *
 159      * @return <code>SchemaFactory</code> for the
 160      * given <code>schemaLanguage</code>.
 161      */
 162     private SchemaFactory _newFactory(String schemaLanguage)
 163             throws FactoryConfigurationError {
 164         SchemaFactory sf;
 165 
 166         String propertyName = SERVICE_CLASS.getName() + ":" + schemaLanguage;
 167 
 168         // system property look up
 169         try {
 170             debugPrintln("Looking up system property '" + propertyName + "'");
 171             String r = ss.getSystemProperty(propertyName);
 172             if (r != null) {
 173                 debugPrintln("The value is '" + r + "'");
 174                 sf = createInstance(r, true);
 175                 if (sf != null) {
 176                     return sf;
 177                 }
 178             } else {
 179                 debugPrintln("The property is undefined.");
 180             }
 181         } catch (Throwable t) {
 182             if (debug) {
 183                 debugPrintln("failed to look up system property '" + propertyName + "'");
 184                 t.printStackTrace();
 185             }
 186         }
 187 
 188         String javah = ss.getSystemProperty("java.home");
 189         String configFile = javah + File.separator
 190                 + "lib" + File.separator + "jaxp.properties";
 191 
 192         String factoryClassName = null;
 193 
 194         // try to read from $java.home/lib/jaxp.properties
 195         try {
 196             if (firstTime) {
 197                 synchronized (cacheProps) {
 198                     if (firstTime) {
 199                         File f = new File(configFile);
 200                         firstTime = false;
 201                         if (ss.doesFileExist(f)) {
 202                             debugPrintln("Read properties file " + f);
 203                             cacheProps.load(ss.getFileInputStream(f));
 204                         }
 205                     }
 206                 }
 207             }
 208             factoryClassName = cacheProps.getProperty(propertyName);
 209             debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties");
 210 
 211             if (factoryClassName != null) {
 212                 sf = createInstance(factoryClassName, true);
 213                 if (sf != null) {
 214                     return sf;
 215                 }
 216             }
 217         } catch (Exception ex) {
 218             if (debug) {
 219                 ex.printStackTrace();
 220             }
 221         }
 222 
 223 
 224         // try finding a service provider
 225         sf = findServiceProvider(schemaLanguage, DEFAULT_IMPL_NAME);
 226         if (sf != null) {
 227             return sf;
 228         }
 229 
 230         // platform default
 231         if (schemaLanguage.equals("http://www.w3.org/2001/XMLSchema")) {
 232             debugPrintln("attempting to use the platform default XML Schema validator");
 233             return createInstance(DEFAULT_IMPL_NAME, true);
 234         }
 235 
 236         debugPrintln("all things were tried, but none was found. bailing out.");
 237         return null;
 238     }
 239 
 240     /**
 241      * <p>Create class using appropriate ClassLoader.</p>
 242      *
 243      * @param className Name of class to create.
 244      * @return Created class or <code>null</code>.
 245      */
 246     private Class createClass(String className) {
 247         Class clazz;
 248 
 249         // use approprite ClassLoader
 250         try {
 251             if (classLoader != null) {
 252                 clazz = classLoader.loadClass(className);
 253             } else {
 254                 clazz = Class.forName(className);
 255             }
 256         } catch (Throwable t) {
 257             if (debug) {
 258                 t.printStackTrace();
 259             }
 260             return null;
 261         }
 262 
 263         return clazz;
 264     }
 265 
 266     /**
 267      * <p>Creates an instance of the specified and returns it.</p>
 268      *
 269      * @param className fully qualified class name to be instanciated.
 270      *
 271      * @return null if it fails. Error messages will be printed by this method.
 272      */
 273     SchemaFactory createInstance(String className) {
 274         return createInstance(className, false);
 275     }
 276 
 277     SchemaFactory createInstance(String className, boolean useServicesMechanism) {
 278         SchemaFactory schemaFactory = null;
 279 
 280         debugPrintln("createInstance(" + className + ")");
 281 
 282         // get Class from className
 283         Class clazz = createClass(className);
 284         if (clazz == null) {
 285             debugPrintln("failed to getClass(" + className + ")");
 286             return null;
 287         }
 288         debugPrintln("loaded " + className + " from " + which(clazz));
 289 
 290         // instantiate Class as a SchemaFactory
 291         try {
 292             if (!useServicesMechanism) {
 293                 schemaFactory = (SchemaFactory) newInstanceNoServiceLoader(clazz);
 294             }
 295             if (schemaFactory == null) {
 296                 schemaFactory = (SchemaFactory) clazz.newInstance();
 297             }
 298         } catch (ClassCastException classCastException) {
 299             debugPrintln("could not instantiate " + clazz.getName());
 300             if (debug) {
 301                 classCastException.printStackTrace();
 302             }
 303             return null;
 304         } catch (IllegalAccessException illegalAccessException) {
 305             debugPrintln("could not instantiate " + clazz.getName());
 306             if (debug) {
 307                 illegalAccessException.printStackTrace();
 308             }
 309             return null;
 310         } catch (InstantiationException instantiationException) {
 311             debugPrintln("could not instantiate " + clazz.getName());
 312             if (debug) {
 313                 instantiationException.printStackTrace();
 314             }
 315             return null;
 316         }
 317 
 318         return schemaFactory;
 319     }
 320 
 321     /**
 322      * Try to construct using newTransformerFactoryNoServiceLoader method if
 323      * available.
 324      */
 325     private static Object newInstanceNoServiceLoader(
 326             Class<?> providerClass) {
 327         // Retain maximum compatibility if no security manager.
 328         if (System.getSecurityManager() == null) {
 329             return null;
 330         }
 331         try {
 332             Method creationMethod =
 333                     providerClass.getDeclaredMethod(
 334                     "newXMLSchemaFactoryNoServiceLoader");
 335             return creationMethod.invoke(null, null);
 336         } catch (NoSuchMethodException exc) {
 337             return null;
 338         } catch (Exception exc) {
 339             return null;
 340         }
 341     }
 342 
 343 
 344     /*
 345      * Try to find a provider using Service Loader
 346      *
 347      * @return instance of provider class if found or null
 348      */
 349     private SchemaFactory findServiceProvider(final String schemaLanguage, final String fallbackClassName)
 350             throws FactoryConfigurationError {
 351         try {
 352             return (SchemaFactory) AccessController.doPrivileged(new PrivilegedAction() {
 353                 public SchemaFactory run() {
 354                     SchemaFactory defaultProvider = null;
 355                     for (SchemaFactory schemaFactory : ServiceLoader.load(SchemaFactory.class, classLoader)) {
 356                         if (schemaFactory.getClass().getName().equals(fallbackClassName)) {
 357                             defaultProvider = schemaFactory;
 358                         } else {
 359                             if (isSchemaSupported(schemaFactory, schemaLanguage)) {
 360                                 return schemaFactory;
 361                             }
 362                         }
 363                     }
 364                     if (defaultProvider != null) {
 365                         return defaultProvider;
 366                     }
 367                     return null;
 368                 }
 369             });
 370         } catch (ServiceConfigurationError e) {
 371             throw new FactoryConfigurationError(e.getMessage(), (Exception) e.getCause());
 372         }
 373     }
 374 
 375     /**
 376      * <p>Test if the specified schemas are supported by the provider.</p>
 377      *
 378      * @param schemaFactory a Schema factory provider.
 379      * @param schemaLanguage Schema Language to support.
 380      *
 381      * @return <code>true</code> if the Schema Language is supported by the
 382      * provider.
 383      */
 384     private static boolean isSchemaSupported(SchemaFactory schemaFactory, String schemaLanguage) {
 385         final Class[] stringClassArray = {"".getClass()};
 386         final Object[] schemaLanguageObjectArray = {schemaLanguage};
 387         final String isSchemaLanguageSupportedMethod = "isSchemaLanguageSupported";
 388 
 389         // does this Class support desired Schema?
 390         try {
 391             Method isSchemaLanguageSupported = schemaFactory.getClass().getMethod(isSchemaLanguageSupportedMethod, stringClassArray);
 392             Boolean supported = (Boolean) isSchemaLanguageSupported.invoke(schemaFactory, schemaLanguageObjectArray);
 393             if (supported.booleanValue()) {
 394                 return true;
 395             }
 396         } catch (NoSuchMethodException noSuchMethodException) {
 397         } catch (IllegalAccessException illegalAccessException) {
 398         } catch (InvocationTargetException invocationTargetException) {
 399         }
 400 
 401         return false;
 402     }
 403     private static final Class SERVICE_CLASS = SchemaFactory.class;
 404 
 405     private static String which(Class clazz) {
 406         return which(clazz.getName(), clazz.getClassLoader());
 407     }
 408 
 409     /**
 410      * <p>Search the specified classloader for the given classname.</p>
 411      *
 412      * @param classname the fully qualified name of the class to search for
 413      * @param loader the classloader to search
 414      *
 415      * @return the source location of the resource, or null if it wasn't found
 416      */
 417     private static String which(String classname, ClassLoader loader) {
 418 
 419         String classnameAsResource = classname.replace('.', '/') + ".class";
 420 
 421         if (loader == null) {
 422             loader = ClassLoader.getSystemClassLoader();
 423         }
 424 
 425         //URL it = loader.getResource(classnameAsResource);
 426         URL it = ss.getResourceAsURL(loader, classnameAsResource);
 427         if (it != null) {
 428             return it.toString();
 429         } else {
 430             return null;
 431         }
 432     }
 433 }