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.BufferedReader;
  29 import java.io.File;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.io.InputStreamReader;
  33 import java.lang.reflect.Method;
  34 import java.lang.reflect.InvocationTargetException;
  35 import java.net.URL;
  36 import java.util.ArrayList;
  37 import java.util.Enumeration;
  38 import java.util.Iterator;
  39 import java.util.NoSuchElementException;
  40 import java.util.Properties;
  41 
  42 /**
  43  * Implementation of {@link SchemaFactory#newInstance(String)}.
  44  *
  45  * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
  46  * @version $Revision: 1.8 $, $Date: 2010-11-01 04:36:13 $
  47  * @since 1.5
  48  */
  49 class SchemaFactoryFinder  {
  50 
  51     /** debug support code. */
  52     private static boolean debug = false;
  53     /**
  54      *<p> Take care of restrictions imposed by java security model </p>
  55      */
  56     private static SecuritySupport ss = new SecuritySupport();
  57     /**
  58      * <p>Cache properties for performance.</p>
  59      */
  60         private static Properties cacheProps = new Properties();
  61 
  62         /**
  63          * <p>First time requires initialization overhead.</p>
  64          */
  65         private static volatile boolean firstTime = true;
  66 
  67     static {
  68         // Use try/catch block to support applets
  69         try {
  70             debug = ss.getSystemProperty("jaxp.debug") != null;
  71         } catch (Exception _) {
  72             debug = false;
  73         }
  74     }
  75 
  76     /**
  77      * <p>Conditional debug printing.</p>
  78      *
  79      * @param msg to print
  80      */
  81     private static void debugPrintln(String msg) {
  82         if (debug) {
  83             System.err.println("JAXP: " + msg);
  84         }
  85     }
  86 
  87     /**
  88      * <p><code>ClassLoader</code> to use to find <code>SchemaFactory</code>.</p>
  89      */
  90     private final ClassLoader classLoader;
  91 
  92     /**
  93      * <p>Constructor that specifies <code>ClassLoader</code> to use
  94      * to find <code>SchemaFactory</code>.</p>
  95      *
  96      * @param loader
  97      *      to be used to load resource, {@link SchemaFactory}, and
  98      *      {@link SchemaFactoryLoader} implementations during
  99      *      the resolution process.
 100      *      If this parameter is null, the default system class loader
 101      *      will be used.
 102      */
 103     public SchemaFactoryFinder(ClassLoader loader) {
 104         this.classLoader = loader;
 105         if( debug ) {
 106             debugDisplayClassLoader();
 107         }
 108     }
 109 
 110     private void debugDisplayClassLoader() {
 111         try {
 112             if( classLoader == ss.getContextClassLoader() ) {
 113                 debugPrintln("using thread context class loader ("+classLoader+") for search");
 114                 return;
 115             }
 116         } catch( Throwable _ ) {
 117             ; // getContextClassLoader() undefined in JDK1.1
 118         }
 119 
 120         if( classLoader==ClassLoader.getSystemClassLoader() ) {
 121             debugPrintln("using system class loader ("+classLoader+") for search");
 122             return;
 123         }
 124 
 125         debugPrintln("using class loader ("+classLoader+") for search");
 126     }
 127 
 128     /**
 129      * <p>Creates a new {@link SchemaFactory} object for the specified
 130      * schema language.</p>
 131      *
 132      * @param schemaLanguage
 133      *      See {@link SchemaFactory Schema Language} table in <code>SchemaFactory</code>
 134      *      for the list of available schema languages.
 135      *
 136      * @return <code>null</code> if the callee fails to create one.
 137      *
 138      * @throws NullPointerException
 139      *      If the <code>schemaLanguage</code> parameter is null.
 140      */
 141     public SchemaFactory newFactory(String schemaLanguage) {
 142         if(schemaLanguage==null)        throw new NullPointerException();
 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 = null ;
 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         /**
 217         // try to read from $java.home/lib/jaxp.properties
 218         try {
 219             String javah = ss.getSystemProperty( "java.home" );
 220             String configFile = javah + File.separator +
 221             "lib" + File.separator + "jaxp.properties";
 222             File f = new File( configFile );
 223             if( ss.doesFileExist(f)) {
 224                 sf = loadFromProperty(
 225                         propertyName,f.getAbsolutePath(), new FileInputStream(f));
 226                 if(sf!=null)    return sf;
 227             } else {
 228                 debugPrintln("Tried to read "+ f.getAbsolutePath()+", but it doesn't exist.");
 229             }
 230         } catch(Throwable e) {
 231             if( debug ) {
 232                 debugPrintln("failed to read $java.home/lib/jaxp.properties");
 233                 e.printStackTrace();
 234             }
 235         }
 236          */
 237 
 238         // try META-INF/services files
 239         Iterator sitr = createServiceFileIterator();
 240         while(sitr.hasNext()) {
 241             URL resource = (URL)sitr.next();
 242             debugPrintln("looking into " + resource);
 243             try {
 244                 sf = loadFromService(schemaLanguage,resource.toExternalForm(),
 245                                                 ss.getURLInputStream(resource));
 246                 if(sf!=null)    return sf;
 247             } catch(IOException e) {
 248                 if( debug ) {
 249                     debugPrintln("failed to read "+resource);
 250                     e.printStackTrace();
 251                 }
 252             }
 253         }
 254 
 255         // platform default
 256         if(schemaLanguage.equals("http://www.w3.org/2001/XMLSchema")) {
 257             debugPrintln("attempting to use the platform default XML Schema validator");
 258             return createInstance("com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory", true);
 259         }
 260 
 261         debugPrintln("all things were tried, but none was found. bailing out.");
 262         return null;
 263     }
 264 
 265     /** <p>Create class using appropriate ClassLoader.</p>
 266      *
 267      * @param className Name of class to create.
 268      * @return Created class or <code>null</code>.
 269      */
 270     private Class createClass(String className) {
 271             Class clazz;
 272 
 273             // use approprite ClassLoader
 274             try {
 275                     if (classLoader != null) {
 276                             clazz = classLoader.loadClass(className);
 277                     } else {
 278                             clazz = Class.forName(className);
 279                     }
 280             } catch (Throwable t) {
 281                 if(debug)   t.printStackTrace();
 282                     return null;
 283             }
 284 
 285             return clazz;
 286     }
 287 
 288     /**
 289      * <p>Creates an instance of the specified and returns it.</p>
 290      *
 291      * @param className
 292      *      fully qualified class name to be instanciated.
 293      *
 294      * @return null
 295      *      if it fails. Error messages will be printed by this method.
 296      */
 297     SchemaFactory createInstance( String className ) {
 298         return createInstance( className, false );
 299     }
 300 
 301     SchemaFactory createInstance( String className, boolean useServicesMechanism ) {
 302         SchemaFactory schemaFactory = null;
 303 
 304         debugPrintln("createInstance(" + className + ")");
 305 
 306         // get Class from className
 307         Class clazz = createClass(className);
 308         if (clazz == null) {
 309                 debugPrintln("failed to getClass(" + className + ")");
 310                 return null;
 311         }
 312         debugPrintln("loaded " + className + " from " + which(clazz));
 313 
 314         // instantiate Class as a SchemaFactory
 315         try {
 316             if (!useServicesMechanism) {
 317                 schemaFactory = (SchemaFactory) newInstanceNoServiceLoader(clazz);
 318             }
 319                 if (schemaFactory == null) {
 320                     schemaFactory = (SchemaFactory) clazz.newInstance();
 321                 }
 322         } catch (ClassCastException classCastException) {
 323                 debugPrintln("could not instantiate " + clazz.getName());
 324                 if (debug) {
 325                         classCastException.printStackTrace();
 326                 }
 327                 return null;
 328         } catch (IllegalAccessException illegalAccessException) {
 329                 debugPrintln("could not instantiate " + clazz.getName());
 330                 if (debug) {
 331                         illegalAccessException.printStackTrace();
 332                 }
 333                 return null;
 334         } catch (InstantiationException instantiationException) {
 335                 debugPrintln("could not instantiate " + clazz.getName());
 336                 if (debug) {
 337                         instantiationException.printStackTrace();
 338                 }
 339                 return null;
 340         }
 341 
 342         return schemaFactory;
 343     }
 344     /**
 345      * Try to construct using newTransformerFactoryNoServiceLoader
 346      *   method if available.
 347      */
 348     private static Object newInstanceNoServiceLoader(
 349          Class<?> providerClass
 350     ) {
 351         // Retain maximum compatibility if no security manager.
 352         if (System.getSecurityManager() == null) {
 353             return null;
 354         }
 355         try {
 356             Method creationMethod =
 357                 providerClass.getDeclaredMethod(
 358                     "newXMLSchemaFactoryNoServiceLoader"
 359                 );
 360                 return creationMethod.invoke(null, null);
 361             } catch (NoSuchMethodException exc) {
 362                 return null;
 363             } catch (Exception exc) {
 364                 return null;
 365         }
 366     }
 367 
 368     /** Iterator that lazily computes one value and returns it. */
 369     private static abstract class SingleIterator implements Iterator {
 370         private boolean seen = false;
 371 
 372         public final void remove() { throw new UnsupportedOperationException(); }
 373         public final boolean hasNext() { return !seen; }
 374         public final Object next() {
 375             if(seen)    throw new NoSuchElementException();
 376             seen = true;
 377             return value();
 378         }
 379 
 380         protected abstract Object value();
 381     }
 382 
 383     /**
 384      * Looks up a value in a property file
 385      * while producing all sorts of debug messages.
 386      *
 387      * @return null
 388      *      if there was an error.
 389      */
 390     private SchemaFactory loadFromProperty( String keyName, String resourceName, InputStream in )
 391         throws IOException {
 392         debugPrintln("Reading "+resourceName );
 393 
 394         Properties props=new Properties();
 395         props.load(in);
 396         in.close();
 397         String factoryClassName = props.getProperty(keyName);
 398         if(factoryClassName != null){
 399             debugPrintln("found "+keyName+" = " + factoryClassName);
 400             return createInstance(factoryClassName);
 401         } else {
 402             debugPrintln(keyName+" is not in the property file");
 403             return null;
 404         }
 405     }
 406 
 407     /**
 408      * <p>Look up a value in a property file.</p>
 409      *
 410      * <p>Set <code>debug</code> to <code>true</code> to trace property evaluation.</p>
 411      *
 412      * @param schemaLanguage Schema Language to support.
 413      * @param inputName Name of <code>InputStream</code>.
 414      * @param in <code>InputStream</code> of properties.
 415      *
 416      * @return <code>SchemaFactory</code> as determined by <code>keyName</code> value or <code>null</code> if there was an error.
 417      *
 418      * @throws IOException If IO error reading from <code>in</code>.
 419      */
 420     private SchemaFactory loadFromService(
 421             String schemaLanguage,
 422             String inputName,
 423             InputStream in)
 424             throws IOException {
 425 
 426             SchemaFactory schemaFactory = null;
 427             final Class[] stringClassArray = {"".getClass()};
 428             final Object[] schemaLanguageObjectArray = {schemaLanguage};
 429             final String isSchemaLanguageSupportedMethod = "isSchemaLanguageSupported";
 430 
 431             debugPrintln("Reading " + inputName);
 432 
 433             // read from InputStream until a match is found
 434             BufferedReader configFile = new BufferedReader(new InputStreamReader(in));
 435             String line = null;
 436             while ((line = configFile.readLine()) != null) {
 437                     // '#' is comment char
 438                     int comment = line.indexOf("#");
 439                     switch (comment) {
 440                             case -1: break; // no comment
 441                             case 0: line = ""; break; // entire line is a comment
 442                             default: line = line.substring(0, comment); break; // trim comment
 443                     }
 444 
 445                     // trim whitespace
 446                     line = line.trim();
 447 
 448                     // any content left on line?
 449                     if (line.length() == 0) {
 450                             continue;
 451                     }
 452 
 453                     // line content is now the name of the class
 454                     Class clazz = createClass(line);
 455                     if (clazz == null) {
 456                             continue;
 457                     }
 458 
 459                     // create an instance of the Class
 460                     try {
 461                             schemaFactory = (SchemaFactory) clazz.newInstance();
 462                     } catch (ClassCastException classCastExcpetion) {
 463                             schemaFactory = null;
 464                             continue;
 465                     } catch (InstantiationException instantiationException) {
 466                             schemaFactory = null;
 467                             continue;
 468                     } catch (IllegalAccessException illegalAccessException) {
 469                             schemaFactory = null;
 470                             continue;
 471                     }
 472 
 473                     // does this Class support desired Schema?
 474                     try {
 475                             Method isSchemaLanguageSupported = clazz.getMethod(isSchemaLanguageSupportedMethod, stringClassArray);
 476                             Boolean supported = (Boolean) isSchemaLanguageSupported.invoke(schemaFactory, schemaLanguageObjectArray);
 477                             if (supported.booleanValue()) {
 478                                     break;
 479                             }
 480                     } catch (NoSuchMethodException noSuchMethodException) {
 481 
 482                     } catch (IllegalAccessException illegalAccessException) {
 483 
 484                     } catch (InvocationTargetException invocationTargetException) {
 485 
 486                     }
 487                     schemaFactory = null;
 488             }
 489 
 490             // clean up
 491             configFile.close();
 492 
 493             // return new instance of SchemaFactory or null
 494             return schemaFactory;
 495     }
 496 
 497     /**
 498      * Returns an {@link Iterator} that enumerates all
 499      * the META-INF/services files that we care.
 500      */
 501     private Iterator createServiceFileIterator() {
 502         if (classLoader == null) {
 503             return new SingleIterator() {
 504                 protected Object value() {
 505                     ClassLoader classLoader = SchemaFactoryFinder.class.getClassLoader();
 506                     //return (ClassLoader.getSystemResource( SERVICE_ID ));
 507                     return ss.getResourceAsURL(classLoader, SERVICE_ID);
 508                 }
 509             };
 510         } else {
 511             try {
 512                 //final Enumeration e = classLoader.getResources(SERVICE_ID);
 513                 final Enumeration e = ss.getResources(classLoader, SERVICE_ID);
 514                 if(!e.hasMoreElements()) {
 515                     debugPrintln("no "+SERVICE_ID+" file was found");
 516                 }
 517 
 518                 // wrap it into an Iterator.
 519                 return new Iterator() {
 520                     public void remove() {
 521                         throw new UnsupportedOperationException();
 522                     }
 523 
 524                     public boolean hasNext() {
 525                         return e.hasMoreElements();
 526                     }
 527 
 528                     public Object next() {
 529                         return e.nextElement();
 530                     }
 531                 };
 532             } catch (IOException e) {
 533                 debugPrintln("failed to enumerate resources "+SERVICE_ID);
 534                 if(debug)   e.printStackTrace();
 535                 return new ArrayList().iterator();  // empty iterator
 536             }
 537         }
 538     }
 539 
 540     private static final Class SERVICE_CLASS = SchemaFactory.class;
 541     private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName();
 542 
 543 
 544 
 545     private static String which( Class clazz ) {
 546         return which( clazz.getName(), clazz.getClassLoader() );
 547     }
 548 
 549     /**
 550      * <p>Search the specified classloader for the given classname.</p>
 551      *
 552      * @param classname the fully qualified name of the class to search for
 553      * @param loader the classloader to search
 554      *
 555      * @return the source location of the resource, or null if it wasn't found
 556      */
 557     private static String which(String classname, ClassLoader loader) {
 558 
 559         String classnameAsResource = classname.replace('.', '/') + ".class";
 560 
 561         if( loader==null )  loader = ClassLoader.getSystemClassLoader();
 562 
 563         //URL it = loader.getResource(classnameAsResource);
 564         URL it = ss.getResourceAsURL(loader, classnameAsResource);
 565         if (it != null) {
 566             return it.toString();
 567         } else {
 568             return null;
 569         }
 570     }
 571 }