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 
18 // $Id: FactoryFinder.java 670432 2008-06-23 02:02:08Z mrglavas $
19 
20 package javax.xml.datatype;
21 
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.net.URL;
29 import java.util.Properties;
30 import libcore.io.IoUtils;
31 
32 /**
33  * <p>Implement pluggable data types.</p>
34  *
35  * <p>This class is duplicated for each JAXP subpackage so keep it in
36  * sync.  It is package private for secure class loading.</p>
37  *
38  * @author <a href="mailto:Jeff.Suttor@Sun.com">Jeff Suttor</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 FactoryFinder {
43 
44     /** <p>Name of class to display in output messages.</p> */
45     private static final String CLASS_NAME = "javax.xml.datatype.FactoryFinder";
46 
47     /** <p>Debug flag to trace loading process.</p> */
48     private static boolean debug = false;
49 
50     /**
51      * <p>Cache properties for performance. Use a static class to avoid double-checked
52      * locking.</p>
53      */
54     private static class CacheHolder {
55 
56         private static Properties cacheProps = new Properties();
57 
58         static {
59             String javah = System.getProperty("java.home");
60             String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties";
61             File f = new File(configFile);
62             if (f.exists()) {
63                 if (debug) debugPrintln("Read properties file " + f);
64                 try {
cacheProps.load(new FileInputStream(f))65                     cacheProps.load(new FileInputStream(f));
66                 } catch (Exception ex) {
67                     if (debug) {
68                         ex.printStackTrace();
69                     }
70                 }
71             }
72         }
73     }
74 
75     /** Default columns per line. */
76     private static final int DEFAULT_LINE_LENGTH = 80;
77 
78     /**
79      * <p>Check to see if debugging enabled by property.</p>
80      *
81      * <p>Use try/catch block to support applets, which throws
82      * SecurityException out of this code.</p>
83      */
84     static {
85         String val = System.getProperty("jaxp.debug");
86         // Allow simply setting the prop to turn on debug
87         debug = val != null && (! "false".equals(val));
88     }
89 
FactoryFinder()90     private FactoryFinder() {}
91 
92     /**
93      * <p>Output debugging messages.</p>
94      *
95      * @param msg <code>String</code> to print to <code>stderr</code>.
96      */
debugPrintln(String msg)97     private static void debugPrintln(String msg) {
98         if (debug) {
99             System.err.println(
100                 CLASS_NAME
101                 + ":"
102                 + msg);
103         }
104     }
105 
106     /**
107      * <p>Find the appropriate <code>ClassLoader</code> to use.</p>
108      *
109      * <p>The context ClassLoader is preferred.</p>
110      *
111      * @return <code>ClassLoader</code> to use.
112      *
113      * @throws ConfigurationError If a valid <code>ClassLoader</code> cannot be identified.
114      */
findClassLoader()115     private static ClassLoader findClassLoader() throws ConfigurationError {
116         // Figure out which ClassLoader to use for loading the provider
117         // class.  If there is a Context ClassLoader then use it.
118 
119         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
120 
121         if (debug) debugPrintln(
122             "Using context class loader: "
123             + classLoader);
124 
125         if (classLoader == null) {
126             // if we have no Context ClassLoader
127             // so use the current ClassLoader
128             classLoader = FactoryFinder.class.getClassLoader();
129             if (debug) debugPrintln(
130                 "Using the class loader of FactoryFinder: "
131                 + classLoader);
132         }
133 
134         return classLoader;
135     }
136 
137     /**
138      * <p>Create an instance of a class using the specified ClassLoader.</p>
139      *
140      * @param className Name of class to create.
141      * @param classLoader ClassLoader to use to create named class.
142      *
143      * @return New instance of specified class created using the specified ClassLoader.
144      *
145      * @throws ConfigurationError If class could not be created.
146      */
newInstance( String className, ClassLoader classLoader)147     static Object newInstance(
148         String className,
149         ClassLoader classLoader)
150         throws ConfigurationError {
151 
152         try {
153             Class spiClass;
154             if (classLoader == null) {
155                 spiClass = Class.forName(className);
156             } else {
157                 spiClass = classLoader.loadClass(className);
158             }
159 
160             if (debug) {
161                 debugPrintln("Loaded " + className + " from " + which(spiClass));
162             }
163 
164             return spiClass.newInstance();
165         } catch (ClassNotFoundException x) {
166             throw new ConfigurationError(
167                 "Provider " + className + " not found", x);
168         } catch (Exception x) {
169             throw new ConfigurationError(
170                 "Provider " + className + " could not be instantiated: " + x, x);
171         }
172     }
173 
174     /**
175      * Finds the implementation Class object in the specified order.  Main
176      * entry point.
177      * Package private so this code can be shared.
178      *
179      * @param factoryId Name of the factory to find, same as a property name
180      * @param fallbackClassName Implementation class name, if nothing else is found.  Use null to mean no fallback.
181      *
182      * @return Class Object of factory, never null
183      *
184      * @throws ConfigurationError If Class cannot be found.
185      */
find(String factoryId, String fallbackClassName)186     static Object find(String factoryId, String fallbackClassName) throws ConfigurationError {
187 
188         ClassLoader classLoader = findClassLoader();
189 
190         // Use the system property first
191         String systemProp = System.getProperty(factoryId);
192         if (systemProp != null && systemProp.length() > 0) {
193             if (debug) debugPrintln("found " + systemProp + " in the system property " + factoryId);
194             return newInstance(systemProp, classLoader);
195         }
196 
197         // try to read from $java.home/lib/jaxp.properties
198         try {
199             String factoryClassName = CacheHolder.cacheProps.getProperty(factoryId);
200             if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties");
201 
202             if (factoryClassName != null) {
203                 return newInstance(factoryClassName, classLoader);
204             }
205         } catch (Exception ex) {
206             if (debug) {
207                 ex.printStackTrace();
208             }
209         }
210 
211         // Try Jar Service Provider Mechanism
212         Object provider = findJarServiceProvider(factoryId);
213         if (provider != null) {
214             return provider;
215         }
216 
217         if (fallbackClassName == null) {
218             throw new ConfigurationError(
219                 "Provider for " + factoryId + " cannot be found", null);
220         }
221 
222         if (debug) debugPrintln("loaded from fallback value: " + fallbackClassName);
223         return newInstance(fallbackClassName, classLoader);
224     }
225 
226     /*
227      * Try to find provider using Jar Service Provider Mechanism
228      *
229      * @return instance of provider class if found or null
230      */
findJarServiceProvider(String factoryId)231     private static Object findJarServiceProvider(String factoryId) throws ConfigurationError {
232         String serviceId = "META-INF/services/" + factoryId;
233         InputStream is = null;
234 
235         // First try the Context ClassLoader
236         ClassLoader cl = Thread.currentThread().getContextClassLoader();
237         if (cl != null) {
238             is = cl.getResourceAsStream(serviceId);
239         }
240 
241         if (is == null) {
242             cl = FactoryFinder.class.getClassLoader();
243             is = cl.getResourceAsStream(serviceId);
244         }
245 
246         if (is == null) {
247             // No provider found
248             return null;
249         }
250 
251         if (debug) debugPrintln("found jar resource=" + serviceId + " using ClassLoader: " + cl);
252 
253         BufferedReader rd;
254         try {
255             rd = new BufferedReader(new InputStreamReader(is, "UTF-8"), DEFAULT_LINE_LENGTH);
256         } catch (java.io.UnsupportedEncodingException e) {
257             rd = new BufferedReader(new InputStreamReader(is), DEFAULT_LINE_LENGTH);
258         }
259 
260         String factoryClassName = null;
261         try {
262             // XXX Does not handle all possible input as specified by the
263             // Jar Service Provider specification
264             factoryClassName = rd.readLine();
265         } catch (IOException x) {
266             // No provider found
267             return null;
268         } finally {
269             IoUtils.closeQuietly(rd);
270         }
271 
272         if (factoryClassName != null &&
273             ! "".equals(factoryClassName)) {
274             if (debug) debugPrintln("found in resource, value="
275                    + factoryClassName);
276 
277             return newInstance(factoryClassName, cl);
278         }
279 
280         // No provider found
281         return null;
282     }
283 
284     /**
285      * <p>Configuration Error.</p>
286      */
287     static class ConfigurationError extends Error {
288 
289         private static final long serialVersionUID = -3644413026244211347L;
290 
291         /**
292          * <p>Exception that caused the error.</p>
293          */
294         private Exception exception;
295 
296         /**
297          * <p>Construct a new instance with the specified detail string and
298          * exception.</p>
299          *
300          * @param msg Detail message for this error.
301          * @param x Exception that caused the error.
302          */
ConfigurationError(String msg, Exception x)303         ConfigurationError(String msg, Exception x) {
304             super(msg);
305             this.exception = x;
306         }
307 
308         /**
309          * <p>Get the Exception that caused the error.</p>
310          *
311          * @return Exception that caused the error.
312          */
getException()313         Exception getException() {
314             return exception;
315         }
316     }
317 
318     /**
319      * Returns the location where the given Class is loaded from.
320      */
which(Class clazz)321     private static String which(Class clazz) {
322         try {
323             String classnameAsResource = clazz.getName().replace('.', '/') + ".class";
324 
325             ClassLoader loader = clazz.getClassLoader();
326 
327             URL it;
328 
329             if (loader != null) {
330                 it = loader.getResource(classnameAsResource);
331             } else {
332                 it = ClassLoader.getSystemResource(classnameAsResource);
333             }
334 
335             if (it != null) {
336                 return it.toString();
337             }
338         }
339         // The VM ran out of memory or there was some other serious problem. Re-throw.
340         catch (VirtualMachineError vme) {
341             throw vme;
342         }
343         // ThreadDeath should always be re-thrown
344         catch (ThreadDeath td) {
345             throw td;
346         }
347         catch (Throwable t) {
348             // work defensively.
349             if (debug) {
350                 t.printStackTrace();
351             }
352         }
353         return "unknown location";
354     }
355 }
356