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