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