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.</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) {
130            throw new NullPointerException("uri == null");
131        }
132        XPathFactory f = _newFactory(uri);
133        if (debug) {
134            if (f != null) {
135                debugPrintln("factory '" + f.getClass().getName() + "' was found for " + uri);
136            } else {
137                debugPrintln("unable to find a factory for " + uri);
138            }
139        }
140        return f;
141    }
142
143    /**
144     * <p>Lookup a {@link XPathFactory} for the given object model.</p>
145     *
146     * @param uri identifies the object model.
147     */
148    private XPathFactory _newFactory(String uri) {
149        XPathFactory xpf;
150        String propertyName = SERVICE_CLASS.getName() + ":" + uri;
151
152        // system property look up
153        try {
154            if (debug) debugPrintln("Looking up system property '"+propertyName+"'" );
155            String r = System.getProperty(propertyName);
156            if (r != null && r.length() > 0) {
157                if (debug) debugPrintln("The value is '"+r+"'");
158                xpf = createInstance(r);
159                if(xpf!=null)    return xpf;
160            } else if (debug) {
161                debugPrintln("The property is undefined.");
162            }
163        } catch (Exception e) {
164            e.printStackTrace();
165        }
166
167        String javah = System.getProperty("java.home");
168        String configFile = javah + File.separator +
169        "lib" + File.separator + "jaxp.properties";
170
171        String factoryClassName = null ;
172
173        // try to read from $java.home/lib/jaxp.properties
174        try {
175            if(firstTime){
176                synchronized(cacheProps){
177                    if(firstTime){
178                        File f=new File( configFile );
179                        firstTime = false;
180                        if (f.exists()) {
181                            if (debug) debugPrintln("Read properties file " + f);
182                            cacheProps.load(new FileInputStream(f));
183                        }
184                    }
185                }
186            }
187            factoryClassName = cacheProps.getProperty(propertyName);
188            if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties");
189
190            if (factoryClassName != null) {
191                xpf = createInstance(factoryClassName);
192                if(xpf != null){
193                    return xpf;
194                }
195            }
196        } catch (Exception ex) {
197            if (debug) {
198                ex.printStackTrace();
199            }
200        }
201
202        // try META-INF/services files
203        for (URL resource : createServiceFileIterator()) {
204            if (debug) debugPrintln("looking into " + resource);
205            try {
206                xpf = loadFromServicesFile(uri, resource.toExternalForm(), resource.openStream());
207                if(xpf!=null)    return xpf;
208            } catch(IOException e) {
209                if( debug ) {
210                    debugPrintln("failed to read "+resource);
211                    e.printStackTrace();
212                }
213            }
214        }
215
216        // platform default
217        if(uri.equals(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) {
218            if (debug) debugPrintln("attempting to use the platform default W3C DOM XPath lib");
219            return createInstance("org.apache.xpath.jaxp.XPathFactoryImpl");
220        }
221
222        if (debug) debugPrintln("all things were tried, but none was found. bailing out.");
223        return null;
224    }
225
226    /**
227     * <p>Creates an instance of the specified and returns it.</p>
228     *
229     * @param className
230     *      fully qualified class name to be instantiated.
231     *
232     * @return null
233     *      if it fails. Error messages will be printed by this method.
234     */
235    XPathFactory createInstance( String className ) {
236        try {
237            if (debug) debugPrintln("instantiating "+className);
238            Class clazz;
239            if( classLoader!=null )
240                clazz = classLoader.loadClass(className);
241            else
242                clazz = Class.forName(className);
243            if(debug)       debugPrintln("loaded it from "+which(clazz));
244            Object o = clazz.newInstance();
245
246            if( o instanceof XPathFactory )
247                return (XPathFactory)o;
248
249            if (debug) debugPrintln(className+" is not assignable to "+SERVICE_CLASS.getName());
250        }
251        // The VM ran out of memory or there was some other serious problem. Re-throw.
252        catch (VirtualMachineError vme) {
253            throw vme;
254        }
255        // ThreadDeath should always be re-thrown
256        catch (ThreadDeath td) {
257            throw td;
258        }
259        catch (Throwable t) {
260            if (debug) {
261                debugPrintln("failed to instantiate "+className);
262                t.printStackTrace();
263            }
264        }
265        return null;
266    }
267
268    /** Searches for a XPathFactory for a given uri in a META-INF/services file. */
269    private XPathFactory loadFromServicesFile(String uri, String resourceName, InputStream in) {
270
271        if (debug) debugPrintln("Reading " + resourceName );
272
273        BufferedReader rd;
274        try {
275            rd = new BufferedReader(new InputStreamReader(in, "UTF-8"), DEFAULT_LINE_LENGTH);
276        } catch (java.io.UnsupportedEncodingException e) {
277            rd = new BufferedReader(new InputStreamReader(in), DEFAULT_LINE_LENGTH);
278        }
279
280        String factoryClassName;
281        XPathFactory resultFactory = null;
282        // See spec for provider-configuration files: http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider%20Configuration%20File
283        while (true) {
284            try {
285                factoryClassName = rd.readLine();
286            } catch (IOException x) {
287                // No provider found
288                break;
289            }
290            if (factoryClassName != null) {
291                // Ignore comments in the provider-configuration file
292                int hashIndex = factoryClassName.indexOf('#');
293                if (hashIndex != -1) {
294                    factoryClassName = factoryClassName.substring(0, hashIndex);
295                }
296
297                // Ignore leading and trailing whitespace
298                factoryClassName = factoryClassName.trim();
299
300                // If there's no text left or if this was a blank line, go to the next one.
301                if (factoryClassName.length() == 0) {
302                    continue;
303                }
304
305                try {
306                    // Found the right XPathFactory if its isObjectModelSupported(String uri) method returns true.
307                    XPathFactory foundFactory = createInstance(factoryClassName);
308                    if (foundFactory.isObjectModelSupported(uri)) {
309                        resultFactory = foundFactory;
310                        break;
311                    }
312                } catch (Exception ignored) {
313                }
314            }
315            else {
316                break;
317            }
318        }
319
320        IoUtils.closeQuietly(rd);
321
322        return resultFactory;
323    }
324
325    /**
326     * Returns an {@link Iterator} that enumerates all
327     * the META-INF/services files that we care.
328     */
329    private Iterable<URL> createServiceFileIterator() {
330        if (classLoader == null) {
331            URL resource = XPathFactoryFinder.class.getClassLoader().getResource(SERVICE_ID);
332            return Collections.singleton(resource);
333        } else {
334            try {
335                Enumeration<URL> e = classLoader.getResources(SERVICE_ID);
336                if (debug && !e.hasMoreElements()) {
337                    debugPrintln("no "+SERVICE_ID+" file was found");
338                }
339
340                return Collections.list(e);
341            } catch (IOException e) {
342                if (debug) {
343                    debugPrintln("failed to enumerate resources "+SERVICE_ID);
344                    e.printStackTrace();
345                }
346                return Collections.emptySet();
347            }
348        }
349    }
350
351    private static final Class SERVICE_CLASS = XPathFactory.class;
352    private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName();
353
354    private static String which( Class clazz ) {
355        return which( clazz.getName(), clazz.getClassLoader() );
356    }
357
358    /**
359     * <p>Search the specified classloader for the given classname.</p>
360     *
361     * @param classname the fully qualified name of the class to search for
362     * @param loader the classloader to search
363     *
364     * @return the source location of the resource, or null if it wasn't found
365     */
366    private static String which(String classname, ClassLoader loader) {
367        String classnameAsResource = classname.replace('.', '/') + ".class";
368        if (loader==null) loader = ClassLoader.getSystemClassLoader();
369
370        URL it = loader.getResource(classnameAsResource);
371        return it != null ? it.toString() : null;
372    }
373}
374