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@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);
try(FileInputStream inputStream = new FileInputStream(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      */
debugPrintln(String msg)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      */
XPathFactoryFinder(ClassLoader loader)110     public XPathFactoryFinder(ClassLoader loader) {
111         this.classLoader = loader;
112         if (debug) {
113             debugDisplayClassLoader();
114         }
115     }
116 
debugDisplayClassLoader()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      */
newFactory(String uri)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      */
_newFactory(String uri)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      */
createInstance( String className )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. */
loadFromServicesFile(String uri, String resourceName, InputStream in)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      */
createServiceFileIterator()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 
which( Class clazz )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      */
which(String classname, ClassLoader loader)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