Home | History | Annotate | Download | only in xpath
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 // $Id: XPathFactoryFinder.java 670432 2008-06-23 02:02:08Z mrglavas $
     18 
     19 package javax.xml.xpath;
     20 
     21 import java.io.BufferedReader;
     22 import java.io.File;
     23 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.io.InputStreamReader;
     26 import java.net.URL;
     27 import java.util.ArrayList;
     28 import java.util.Enumeration;
     29 import java.util.Iterator;
     30 import java.util.NoSuchElementException;
     31 import java.util.Properties;
     32 import javax.xml.validation.SchemaFactory;
     33 
     34 /**
     35  * Implementation of {@link XPathFactory#newInstance(String)}.
     36  *
     37  * @author <a href="Kohsuke.Kawaguchi (at) Sun.com">Kohsuke Kawaguchi</a>
     38  * @version $Revision: 670432 $, $Date: 2008-06-22 19:02:08 -0700 (Sun, 22 Jun 2008) $
     39  * @since 1.5
     40  */
     41 final class XPathFactoryFinder {
     42 
     43     /** debug support code. */
     44     private static boolean debug = false;
     45 
     46     /**
     47      * Default columns per line.
     48      */
     49     private static final int DEFAULT_LINE_LENGTH = 80;
     50 
     51     static {
     52         // Use try/catch block to support applets
     53         try {
     54             String val = SecuritySupport.getSystemProperty("jaxp.debug");
     55             // Allow simply setting the prop to turn on debug
     56             debug = val != null && (! "false".equals(val));
     57         } catch (Exception _) {
     58             debug = false;
     59         }
     60     }
     61 
     62     /**
     63      * <p>Cache properties for performance.</p>
     64      */
     65     private static Properties cacheProps = new Properties();
     66 
     67     /**
     68      * <p>First time requires initialization overhead.</p>
     69      */
     70     private static boolean firstTime = true;
     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 XPathFactoryFinder(ClassLoader loader) {
    100         this.classLoader = loader;
    101         if( debug ) {
    102             debugDisplayClassLoader();
    103         }
    104     }
    105 
    106     private void debugDisplayClassLoader() {
    107         try {
    108             if( classLoader == SecuritySupport.getContextClassLoader() ) {
    109                 debugPrintln("using thread context class loader ("+classLoader+") for search");
    110                 return;
    111             }
    112         }
    113         // The VM ran out of memory or there was some other serious problem. Re-throw.
    114         catch (VirtualMachineError vme) {
    115             throw vme;
    116         }
    117         // ThreadDeath should always be re-thrown
    118         catch (ThreadDeath td) {
    119             throw td;
    120         }
    121         catch (Throwable _) {
    122             ; // getContextClassLoader() undefined in JDK1.1
    123         }
    124 
    125         if( classLoader==ClassLoader.getSystemClassLoader() ) {
    126             debugPrintln("using system class loader ("+classLoader+") for search");
    127             return;
    128         }
    129 
    130         debugPrintln("using class loader ("+classLoader+") for search");
    131     }
    132 
    133     /**
    134      * <p>Creates a new {@link XPathFactory} object for the specified
    135      * schema language.</p>
    136      *
    137      * @param uri
    138      *       Identifies the underlying object model.
    139      *
    140      * @return <code>null</code> if the callee fails to create one.
    141      *
    142      * @throws NullPointerException
    143      *      If the parameter is null.
    144      */
    145     public XPathFactory newFactory(String uri) {
    146         if(uri==null)        throw new NullPointerException();
    147         XPathFactory f = _newFactory(uri);
    148         if (debug) {
    149             if (f != null) {
    150                 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + uri);
    151             } else {
    152                 debugPrintln("unable to find a factory for " + uri);
    153             }
    154         }
    155         return f;
    156     }
    157 
    158     /**
    159      * <p>Lookup a {@link XPathFactory} for the given object model.</p>
    160      *
    161      * @param uri identifies the object model.
    162      *
    163      * @return {@link XPathFactory} for the given object model.
    164      */
    165     private XPathFactory _newFactory(String uri) {
    166         XPathFactory xpf;
    167 
    168         String propertyName = SERVICE_CLASS.getName() + ":" + uri;
    169 
    170         // system property look up
    171         try {
    172             if (debug) debugPrintln("Looking up system property '"+propertyName+"'" );
    173             String r = SecuritySupport.getSystemProperty(propertyName);
    174             if (r != null && r.length() > 0) {
    175                 if (debug) debugPrintln("The value is '"+r+"'");
    176                 xpf = createInstance(r);
    177                 if(xpf!=null)    return xpf;
    178             }
    179             else if (debug) {
    180                 debugPrintln("The property is undefined.");
    181             }
    182         }
    183         // The VM ran out of memory or there was some other serious problem. Re-throw.
    184         catch (VirtualMachineError vme) {
    185             throw vme;
    186         }
    187         // ThreadDeath should always be re-thrown
    188         catch (ThreadDeath td) {
    189             throw td;
    190         }
    191         catch (Throwable t) {
    192             if( debug ) {
    193                 debugPrintln("failed to look up system property '"+propertyName+"'" );
    194                 t.printStackTrace();
    195             }
    196         }
    197 
    198         String javah = SecuritySupport.getSystemProperty( "java.home" );
    199         String configFile = javah + File.separator +
    200         "lib" + File.separator + "jaxp.properties";
    201 
    202         String factoryClassName = null ;
    203 
    204         // try to read from $java.home/lib/jaxp.properties
    205         try {
    206             if(firstTime){
    207                 synchronized(cacheProps){
    208                     if(firstTime){
    209                         File f=new File( configFile );
    210                         firstTime = false;
    211                         if(SecuritySupport.doesFileExist(f)){
    212                             if (debug) debugPrintln("Read properties file " + f);
    213                             cacheProps.load(SecuritySupport.getFileInputStream(f));
    214                         }
    215                     }
    216                 }
    217             }
    218             factoryClassName = cacheProps.getProperty(propertyName);
    219             if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties");
    220 
    221             if (factoryClassName != null) {
    222                 xpf = createInstance(factoryClassName);
    223                 if(xpf != null){
    224                     return xpf;
    225                 }
    226             }
    227         } catch (Exception ex) {
    228             if (debug) {
    229                 ex.printStackTrace();
    230             }
    231         }
    232 
    233         // try META-INF/services files
    234         Iterator sitr = createServiceFileIterator();
    235         while(sitr.hasNext()) {
    236             URL resource = (URL)sitr.next();
    237             if (debug) debugPrintln("looking into " + resource);
    238             try {
    239                 xpf = loadFromServicesFile(uri, resource.toExternalForm(), SecuritySupport.getURLInputStream(resource));
    240                 if(xpf!=null)    return xpf;
    241             } catch(IOException e) {
    242                 if( debug ) {
    243                     debugPrintln("failed to read "+resource);
    244                     e.printStackTrace();
    245                 }
    246             }
    247         }
    248 
    249         // platform default
    250         if(uri.equals(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) {
    251             if (debug) debugPrintln("attempting to use the platform default W3C DOM XPath lib");
    252             return createInstance("org.apache.xpath.jaxp.XPathFactoryImpl");
    253         }
    254 
    255         if (debug) debugPrintln("all things were tried, but none was found. bailing out.");
    256         return null;
    257     }
    258 
    259     /**
    260      * <p>Creates an instance of the specified and returns it.</p>
    261      *
    262      * @param className
    263      *      fully qualified class name to be instanciated.
    264      *
    265      * @return null
    266      *      if it fails. Error messages will be printed by this method.
    267      */
    268     XPathFactory createInstance( String className ) {
    269         try {
    270             if (debug) debugPrintln("instanciating "+className);
    271             Class clazz;
    272             if( classLoader!=null )
    273                 clazz = classLoader.loadClass(className);
    274             else
    275                 clazz = Class.forName(className);
    276             if(debug)       debugPrintln("loaded it from "+which(clazz));
    277             Object o = clazz.newInstance();
    278 
    279             if( o instanceof XPathFactory )
    280                 return (XPathFactory)o;
    281 
    282             if (debug) debugPrintln(className+" is not assignable to "+SERVICE_CLASS.getName());
    283         }
    284         // The VM ran out of memory or there was some other serious problem. Re-throw.
    285         catch (VirtualMachineError vme) {
    286             throw vme;
    287         }
    288         // ThreadDeath should always be re-thrown
    289         catch (ThreadDeath td) {
    290             throw td;
    291         }
    292         catch (Throwable t) {
    293             if (debug) {
    294                 debugPrintln("failed to instanciate "+className);
    295                 t.printStackTrace();
    296             }
    297         }
    298         return null;
    299     }
    300 
    301     /** Iterator that lazily computes one value and returns it. */
    302     private static abstract class SingleIterator implements Iterator {
    303         private boolean seen = false;
    304 
    305         public final void remove() { throw new UnsupportedOperationException(); }
    306         public final boolean hasNext() { return !seen; }
    307         public final Object next() {
    308             if(seen)    throw new NoSuchElementException();
    309             seen = true;
    310             return value();
    311         }
    312 
    313         protected abstract Object value();
    314     }
    315 
    316     /** Searches for a XPathFactory for a given uri in a META-INF/services file. */
    317     private XPathFactory loadFromServicesFile(String uri, String resourceName, InputStream in) {
    318 
    319         if (debug) debugPrintln("Reading " + resourceName );
    320 
    321         BufferedReader rd;
    322         try {
    323             rd = new BufferedReader(new InputStreamReader(in, "UTF-8"), DEFAULT_LINE_LENGTH);
    324         } catch (java.io.UnsupportedEncodingException e) {
    325             rd = new BufferedReader(new InputStreamReader(in), DEFAULT_LINE_LENGTH);
    326         }
    327 
    328         String factoryClassName = null;
    329         XPathFactory resultFactory = null;
    330         // See spec for provider-configuration files: http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider%20Configuration%20File
    331         while (true) {
    332             try {
    333                 factoryClassName = rd.readLine();
    334             } catch (IOException x) {
    335                 // No provider found
    336                 break;
    337             }
    338             if (factoryClassName != null) {
    339                 // Ignore comments in the provider-configuration file
    340                 int hashIndex = factoryClassName.indexOf('#');
    341                 if (hashIndex != -1) {
    342                     factoryClassName = factoryClassName.substring(0, hashIndex);
    343                 }
    344 
    345                 // Ignore leading and trailing whitespace
    346                 factoryClassName = factoryClassName.trim();
    347 
    348                 // If there's no text left or if this was a blank line, go to the next one.
    349                 if (factoryClassName.length() == 0) {
    350                     continue;
    351                 }
    352 
    353                 try {
    354                     // Found the right XPathFactory if its isObjectModelSupported(String uri) method returns true.
    355                     XPathFactory foundFactory = (XPathFactory) createInstance(factoryClassName);
    356                     if (foundFactory.isObjectModelSupported(uri)) {
    357                         resultFactory = foundFactory;
    358                         break;
    359                     }
    360                 }
    361                 catch (Exception e) {}
    362             }
    363             else {
    364                 break;
    365             }
    366         }
    367 
    368         try {
    369             // try to close the reader.
    370             rd.close();
    371         }
    372         // Ignore the exception.
    373         catch (IOException exc) {}
    374 
    375         return resultFactory;
    376     }
    377 
    378     /**
    379      * Returns an {@link Iterator} that enumerates all
    380      * the META-INF/services files that we care.
    381      */
    382     private Iterator createServiceFileIterator() {
    383         if (classLoader == null) {
    384             return new SingleIterator() {
    385                 protected Object value() {
    386                     ClassLoader classLoader = XPathFactoryFinder.class.getClassLoader();
    387                     return SecuritySupport.getResourceAsURL(classLoader, SERVICE_ID);
    388                     //return (ClassLoader.getSystemResource( SERVICE_ID ));
    389                 }
    390             };
    391         } else {
    392             try {
    393                 //final Enumeration e = classLoader.getResources(SERVICE_ID);
    394                 final Enumeration e = SecuritySupport.getResources(classLoader, SERVICE_ID);
    395                 if (debug && !e.hasMoreElements()) {
    396                     debugPrintln("no "+SERVICE_ID+" file was found");
    397                 }
    398 
    399                 // wrap it into an Iterator.
    400                 return new Iterator() {
    401                     public void remove() {
    402                         throw new UnsupportedOperationException();
    403                     }
    404 
    405                     public boolean hasNext() {
    406                         return e.hasMoreElements();
    407                     }
    408 
    409                     public Object next() {
    410                         return e.nextElement();
    411                     }
    412                 };
    413             } catch (IOException e) {
    414                 if (debug) {
    415                     debugPrintln("failed to enumerate resources "+SERVICE_ID);
    416                     e.printStackTrace();
    417                 }
    418                 return new ArrayList().iterator();  // empty iterator
    419             }
    420         }
    421     }
    422 
    423     private static final Class SERVICE_CLASS = XPathFactory.class;
    424     private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName();
    425 
    426 
    427 
    428     private static String which( Class clazz ) {
    429         return which( clazz.getName(), clazz.getClassLoader() );
    430     }
    431 
    432     /**
    433      * <p>Search the specified classloader for the given classname.</p>
    434      *
    435      * @param classname the fully qualified name of the class to search for
    436      * @param loader the classloader to search
    437      *
    438      * @return the source location of the resource, or null if it wasn't found
    439      */
    440     private static String which(String classname, ClassLoader loader) {
    441 
    442         String classnameAsResource = classname.replace('.', '/') + ".class";
    443 
    444         if( loader==null )  loader = ClassLoader.getSystemClassLoader();
    445 
    446         //URL it = loader.getResource(classnameAsResource);
    447         URL it = SecuritySupport.getResourceAsURL(loader, classnameAsResource);
    448         if (it != null) {
    449             return it.toString();
    450         } else {
    451             return null;
    452         }
    453     }
    454 }
    455