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
19package javax.xml.xpath;
20
21import java.io.BufferedReader;
22import java.io.File;
23import java.io.FileInputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.InputStreamReader;
27import java.net.URL;
28import java.util.Collections;
29import java.util.Enumeration;
30import java.util.Iterator;
31import java.util.Properties;
32import javax.xml.validation.SchemaFactory;
33import 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 */
42final 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 {
73                    cacheProps.load(new FileInputStream(f));
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