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.FileInputStream;
     24 import java.io.IOException;
     25 import java.io.InputStream;
     26 import java.io.InputStreamReader;
     27 import java.net.URL;
     28 import java.util.Collections;
     29 import java.util.Enumeration;
     30 import java.util.Iterator;
     31 import java.util.Properties;
     32 import javax.xml.validation.SchemaFactory;
     33 import libcore.io.IoUtils;
     34 
     35 /**
     36  * Implementation of {@link XPathFactory#newInstance(String)}.
     37  *
     38  * @author <a href="Kohsuke.Kawaguchi (at) Sun.com">Kohsuke Kawaguchi</a>
     39  * @version $Revision: 670432 $, $Date: 2008-06-22 19:02:08 -0700 (Sun, 22 Jun 2008) $
     40  * @since 1.5
     41  */
     42 final class XPathFactoryFinder {
     43 
     44     /** debug support code. */
     45     private static boolean debug = false;
     46 
     47     /**
     48      * Default columns per line.
     49      */
     50     private static final int DEFAULT_LINE_LENGTH = 80;
     51 
     52     static {
     53         String val = System.getProperty("jaxp.debug");
     54         // Allow simply setting the prop to turn on debug
     55         debug = val != null && (! "false".equals(val));
     56     }
     57 
     58     /**
     59      * <p>Cache properties for performance.</p>
     60      */
     61     private static Properties cacheProps = new Properties();
     62 
     63     /**
     64      * <p>First time requires initialization overhead.</p>
     65      */
     66     private static boolean firstTime = true;
     67 
     68     /**
     69      * <p>Conditional debug printing.</p>
     70      *
     71      * @param msg to print
     72      */
     73     private static void debugPrintln(String msg) {
     74         if (debug) {
     75             System.err.println("JAXP: " + msg);
     76         }
     77     }
     78 
     79     /**
     80      * <p><code>ClassLoader</code> to use to find <code>SchemaFactory</code>.</p>
     81      */
     82     private final ClassLoader classLoader;
     83 
     84     /**
     85      * <p>Constructor that specifies <code>ClassLoader</code> to use
     86      * to find <code>SchemaFactory</code>.</p>
     87      *
     88      * @param loader
     89      *      to be used to load resource, {@link SchemaFactory}, and
     90      *      {@code SchemaFactoryLoader} implementations during
     91      *      the resolution process.
     92      *      If this parameter is null, the default system class loader
     93      *      will be used.
     94      */
     95     public XPathFactoryFinder(ClassLoader loader) {
     96         this.classLoader = loader;
     97         if (debug) {
     98             debugDisplayClassLoader();
     99         }
    100     }
    101 
    102     private void debugDisplayClassLoader() {
    103         if (classLoader == Thread.currentThread().getContextClassLoader()) {
    104             debugPrintln("using thread context class loader (" + classLoader + ") for search");
    105             return;
    106         }
    107 
    108         if (classLoader==ClassLoader.getSystemClassLoader()) {
    109             debugPrintln("using system class loader (" + classLoader + ") for search");
    110             return;
    111         }
    112 
    113         debugPrintln("using class loader (" + classLoader + ") for search");
    114     }
    115 
    116     /**
    117      * <p>Creates a new {@link XPathFactory} object for the specified
    118      * schema language.</p>
    119      *
    120      * @param uri
    121      *       Identifies the underlying object model.
    122      *
    123      * @return <code>null</code> if the callee fails to create one.
    124      *
    125      * @throws NullPointerException
    126      *      If the parameter is null.
    127      */
    128     public XPathFactory newFactory(String uri) {
    129         if(uri==null)        throw new NullPointerException();
    130         XPathFactory f = _newFactory(uri);
    131         if (debug) {
    132             if (f != null) {
    133                 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + uri);
    134             } else {
    135                 debugPrintln("unable to find a factory for " + uri);
    136             }
    137         }
    138         return f;
    139     }
    140 
    141     /**
    142      * <p>Lookup a {@link XPathFactory} for the given object model.</p>
    143      *
    144      * @param uri identifies the object model.
    145      */
    146     private XPathFactory _newFactory(String uri) {
    147         XPathFactory xpf;
    148         String propertyName = SERVICE_CLASS.getName() + ":" + uri;
    149 
    150         // system property look up
    151         try {
    152             if (debug) debugPrintln("Looking up system property '"+propertyName+"'" );
    153             String r = System.getProperty(propertyName);
    154             if (r != null && r.length() > 0) {
    155                 if (debug) debugPrintln("The value is '"+r+"'");
    156                 xpf = createInstance(r);
    157                 if(xpf!=null)    return xpf;
    158             } else if (debug) {
    159                 debugPrintln("The property is undefined.");
    160             }
    161         } catch (Exception e) {
    162             e.printStackTrace();
    163         }
    164 
    165         String javah = System.getProperty("java.home");
    166         String configFile = javah + File.separator +
    167         "lib" + File.separator + "jaxp.properties";
    168 
    169         String factoryClassName = null ;
    170 
    171         // try to read from $java.home/lib/jaxp.properties
    172         try {
    173             if(firstTime){
    174                 synchronized(cacheProps){
    175                     if(firstTime){
    176                         File f=new File( configFile );
    177                         firstTime = false;
    178                         if (f.exists()) {
    179                             if (debug) debugPrintln("Read properties file " + f);
    180                             cacheProps.load(new FileInputStream(f));
    181                         }
    182                     }
    183                 }
    184             }
    185             factoryClassName = cacheProps.getProperty(propertyName);
    186             if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties");
    187 
    188             if (factoryClassName != null) {
    189                 xpf = createInstance(factoryClassName);
    190                 if(xpf != null){
    191                     return xpf;
    192                 }
    193             }
    194         } catch (Exception ex) {
    195             if (debug) {
    196                 ex.printStackTrace();
    197             }
    198         }
    199 
    200         // try META-INF/services files
    201         for (URL resource : createServiceFileIterator()) {
    202             if (debug) debugPrintln("looking into " + resource);
    203             try {
    204                 xpf = loadFromServicesFile(uri, resource.toExternalForm(), resource.openStream());
    205                 if(xpf!=null)    return xpf;
    206             } catch(IOException e) {
    207                 if( debug ) {
    208                     debugPrintln("failed to read "+resource);
    209                     e.printStackTrace();
    210                 }
    211             }
    212         }
    213 
    214         // platform default
    215         if(uri.equals(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) {
    216             if (debug) debugPrintln("attempting to use the platform default W3C DOM XPath lib");
    217             return createInstance("org.apache.xpath.jaxp.XPathFactoryImpl");
    218         }
    219 
    220         if (debug) debugPrintln("all things were tried, but none was found. bailing out.");
    221         return null;
    222     }
    223 
    224     /**
    225      * <p>Creates an instance of the specified and returns it.</p>
    226      *
    227      * @param className
    228      *      fully qualified class name to be instantiated.
    229      *
    230      * @return null
    231      *      if it fails. Error messages will be printed by this method.
    232      */
    233     XPathFactory createInstance( String className ) {
    234         try {
    235             if (debug) debugPrintln("instantiating "+className);
    236             Class clazz;
    237             if( classLoader!=null )
    238                 clazz = classLoader.loadClass(className);
    239             else
    240                 clazz = Class.forName(className);
    241             if(debug)       debugPrintln("loaded it from "+which(clazz));
    242             Object o = clazz.newInstance();
    243 
    244             if( o instanceof XPathFactory )
    245                 return (XPathFactory)o;
    246 
    247             if (debug) debugPrintln(className+" is not assignable to "+SERVICE_CLASS.getName());
    248         }
    249         // The VM ran out of memory or there was some other serious problem. Re-throw.
    250         catch (VirtualMachineError vme) {
    251             throw vme;
    252         }
    253         // ThreadDeath should always be re-thrown
    254         catch (ThreadDeath td) {
    255             throw td;
    256         }
    257         catch (Throwable t) {
    258             if (debug) {
    259                 debugPrintln("failed to instantiate "+className);
    260                 t.printStackTrace();
    261             }
    262         }
    263         return null;
    264     }
    265 
    266     /** Searches for a XPathFactory for a given uri in a META-INF/services file. */
    267     private XPathFactory loadFromServicesFile(String uri, String resourceName, InputStream in) {
    268 
    269         if (debug) debugPrintln("Reading " + resourceName );
    270 
    271         BufferedReader rd;
    272         try {
    273             rd = new BufferedReader(new InputStreamReader(in, "UTF-8"), DEFAULT_LINE_LENGTH);
    274         } catch (java.io.UnsupportedEncodingException e) {
    275             rd = new BufferedReader(new InputStreamReader(in), DEFAULT_LINE_LENGTH);
    276         }
    277 
    278         String factoryClassName;
    279         XPathFactory resultFactory = null;
    280         // See spec for provider-configuration files: http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider%20Configuration%20File
    281         while (true) {
    282             try {
    283                 factoryClassName = rd.readLine();
    284             } catch (IOException x) {
    285                 // No provider found
    286                 break;
    287             }
    288             if (factoryClassName != null) {
    289                 // Ignore comments in the provider-configuration file
    290                 int hashIndex = factoryClassName.indexOf('#');
    291                 if (hashIndex != -1) {
    292                     factoryClassName = factoryClassName.substring(0, hashIndex);
    293                 }
    294 
    295                 // Ignore leading and trailing whitespace
    296                 factoryClassName = factoryClassName.trim();
    297 
    298                 // If there's no text left or if this was a blank line, go to the next one.
    299                 if (factoryClassName.length() == 0) {
    300                     continue;
    301                 }
    302 
    303                 try {
    304                     // Found the right XPathFactory if its isObjectModelSupported(String uri) method returns true.
    305                     XPathFactory foundFactory = createInstance(factoryClassName);
    306                     if (foundFactory.isObjectModelSupported(uri)) {
    307                         resultFactory = foundFactory;
    308                         break;
    309                     }
    310                 } catch (Exception ignored) {
    311                 }
    312             }
    313             else {
    314                 break;
    315             }
    316         }
    317 
    318         IoUtils.closeQuietly(rd);
    319 
    320         return resultFactory;
    321     }
    322 
    323     /**
    324      * Returns an {@link Iterator} that enumerates all
    325      * the META-INF/services files that we care.
    326      */
    327     private Iterable<URL> createServiceFileIterator() {
    328         if (classLoader == null) {
    329             URL resource = XPathFactoryFinder.class.getClassLoader().getResource(SERVICE_ID);
    330             return Collections.singleton(resource);
    331         } else {
    332             try {
    333                 Enumeration<URL> e = classLoader.getResources(SERVICE_ID);
    334                 if (debug && !e.hasMoreElements()) {
    335                     debugPrintln("no "+SERVICE_ID+" file was found");
    336                 }
    337 
    338                 return Collections.list(e);
    339             } catch (IOException e) {
    340                 if (debug) {
    341                     debugPrintln("failed to enumerate resources "+SERVICE_ID);
    342                     e.printStackTrace();
    343                 }
    344                 return Collections.emptySet();
    345             }
    346         }
    347     }
    348 
    349     private static final Class SERVICE_CLASS = XPathFactory.class;
    350     private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName();
    351 
    352     private static String which( Class clazz ) {
    353         return which( clazz.getName(), clazz.getClassLoader() );
    354     }
    355 
    356     /**
    357      * <p>Search the specified classloader for the given classname.</p>
    358      *
    359      * @param classname the fully qualified name of the class to search for
    360      * @param loader the classloader to search
    361      *
    362      * @return the source location of the resource, or null if it wasn't found
    363      */
    364     private static String which(String classname, ClassLoader loader) {
    365         String classnameAsResource = classname.replace('.', '/') + ".class";
    366         if (loader==null) loader = ClassLoader.getSystemClassLoader();
    367 
    368         URL it = loader.getResource(classnameAsResource);
    369         return it != null ? it.toString() : null;
    370     }
    371 }
    372