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: SchemaFactoryFinder.java 727367 2008-12-17 13:05:26Z mrglavas $
18 
19 package javax.xml.validation;
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.XMLConstants;
33 import libcore.io.IoUtils;
34 
35 /**
36  * Implementation of {@link SchemaFactory#newInstance(String)}.
37  *
38  * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
39  * @version $Revision: 727367 $, $Date: 2008-12-17 05:05:26 -0800 (Wed, 17 Dec 2008) $
40  * @since 1.5
41  */
42 final class SchemaFactoryFinder  {
43 
44     /** XML Schema language identifiers. */
45     private static final String W3C_XML_SCHEMA10_NS_URI = "http://www.w3.org/XML/XMLSchema/v1.0";
46     private static final String W3C_XML_SCHEMA11_NS_URI = "http://www.w3.org/XML/XMLSchema/v1.1";
47 
48     /** debug support code. */
49     private static boolean debug = false;
50 
51     /**
52      * <p>Cache properties for performance. Use a static class to avoid double-checked
53      * locking.</p>
54      */
55     private static class CacheHolder {
56 
57         private static Properties cacheProps = new Properties();
58 
59         static {
60             String javah = System.getProperty("java.home");
61             String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties";
62             File f = new File(configFile);
63             if (f.exists()) {
64                 if (debug) debugPrintln("Read properties file " + f);
65                 try {
cacheProps.load(new FileInputStream(f))66                     cacheProps.load(new FileInputStream(f));
67                 } catch (Exception ex) {
68                     if (debug) {
69                         ex.printStackTrace();
70                     }
71                 }
72             }
73         }
74     }
75 
76     /**
77      * Default columns per line.
78      */
79     private static final int DEFAULT_LINE_LENGTH = 80;
80 
81     static {
82         String val = System.getProperty("jaxp.debug");
83         // Allow simply setting the prop to turn on debug
84         debug = val != null && (! "false".equals(val));
85     }
86 
87     /**
88      * <p>Conditional debug printing.</p>
89      *
90      * @param msg to print
91      */
debugPrintln(String msg)92     private static void debugPrintln(String msg) {
93         if (debug) {
94             System.err.println("JAXP: " + msg);
95         }
96     }
97 
98     /**
99      * <p><code>ClassLoader</code> to use to find <code>SchemaFactory</code>.</p>
100      */
101     private final ClassLoader classLoader;
102 
103     /**
104      * <p>Constructor that specifies <code>ClassLoader</code> to use
105      * to find <code>SchemaFactory</code>.</p>
106      *
107      * @param loader
108      *      to be used to load resource, {@link SchemaFactory}, and
109      *      {@link SchemaFactoryLoader} implementations during
110      *      the resolution process.
111      *      If this parameter is null, the default system class loader
112      *      will be used.
113      */
SchemaFactoryFinder(ClassLoader loader)114     public SchemaFactoryFinder(ClassLoader loader) {
115         this.classLoader = loader;
116         if( debug ) {
117             debugDisplayClassLoader();
118         }
119     }
120 
debugDisplayClassLoader()121     private void debugDisplayClassLoader() {
122         if (classLoader == Thread.currentThread().getContextClassLoader()) {
123             debugPrintln("using thread context class loader ("+classLoader+") for search");
124             return;
125         }
126 
127         if (classLoader == ClassLoader.getSystemClassLoader()) {
128             debugPrintln("using system class loader ("+classLoader+") for search");
129             return;
130         }
131 
132         debugPrintln("using class loader (" + classLoader + ") for search");
133     }
134 
135     /**
136      * <p>Creates a new {@link SchemaFactory} object for the specified
137      * schema language.</p>
138      *
139      * @param schemaLanguage
140      *      See {@link SchemaFactory Schema Language} table in <code>SchemaFactory</code>
141      *      for the list of available schema languages.
142      *
143      * @return <code>null</code> if the callee fails to create one.
144      *
145      * @throws NullPointerException
146      *      If the <tt>schemaLanguage</tt> parameter is null.
147      */
newFactory(String schemaLanguage)148     public SchemaFactory newFactory(String schemaLanguage) {
149         if (schemaLanguage == null) {
150             throw new NullPointerException("schemaLanguage == null");
151         }
152         SchemaFactory f = _newFactory(schemaLanguage);
153         if (debug) {
154             if (f != null) {
155                 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + schemaLanguage);
156             } else {
157                 debugPrintln("unable to find a factory for " + schemaLanguage);
158             }
159         }
160         return f;
161     }
162 
163     /**
164      * <p>Lookup a <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.</p>
165      *
166      * @param schemaLanguage Schema language to lookup <code>SchemaFactory</code> for.
167      *
168      * @return <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.
169      */
_newFactory(String schemaLanguage)170     private SchemaFactory _newFactory(String schemaLanguage) {
171         SchemaFactory sf;
172         String propertyName = SERVICE_CLASS.getName() + ":" + schemaLanguage;
173 
174         // system property look up
175         try {
176             if (debug) debugPrintln("Looking up system property '"+propertyName+"'" );
177             String r = System.getProperty(propertyName);
178             if (r != null && r.length() > 0) {
179                 if (debug) debugPrintln("The value is '"+r+"'");
180                 sf = createInstance(r);
181                 if(sf!=null)    return sf;
182             }
183             else if (debug) {
184                 debugPrintln("The property is undefined.");
185             }
186         }
187         // The VM ran out of memory or there was some other serious problem. Re-throw.
188         catch (VirtualMachineError vme) {
189             throw vme;
190         }
191         // ThreadDeath should always be re-thrown
192         catch (ThreadDeath td) {
193             throw td;
194         }
195         catch (Throwable t) {
196             if( debug ) {
197                 debugPrintln("failed to look up system property '"+propertyName+"'" );
198                 t.printStackTrace();
199             }
200         }
201 
202         // try to read from $java.home/lib/jaxp.properties
203         try {
204             String factoryClassName = CacheHolder.cacheProps.getProperty(propertyName);
205             if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties");
206 
207             if (factoryClassName != null) {
208                 sf = createInstance(factoryClassName);
209                 if(sf != null){
210                     return sf;
211                 }
212             }
213         } catch (Exception ex) {
214             if (debug) {
215                 ex.printStackTrace();
216             }
217         }
218 
219         // try META-INF/services files
220         for (URL resource : createServiceFileIterator()) {
221             if (debug) debugPrintln("looking into " + resource);
222             try {
223                 sf = loadFromServicesFile(schemaLanguage,resource.toExternalForm(),
224                         resource.openStream());
225                 if(sf!=null)    return sf;
226             } catch(IOException e) {
227                 if( debug ) {
228                     debugPrintln("failed to read "+resource);
229                     e.printStackTrace();
230                 }
231             }
232         }
233 
234         // platform defaults
235         if (schemaLanguage.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI) || schemaLanguage.equals(W3C_XML_SCHEMA10_NS_URI)) {
236             if (debug) debugPrintln("attempting to use the platform default XML Schema 1.0 validator");
237             return createInstance("org.apache.xerces.jaxp.validation.XMLSchemaFactory");
238         }
239         else if (schemaLanguage.equals(W3C_XML_SCHEMA11_NS_URI)) {
240             if (debug) debugPrintln("attempting to use the platform default XML Schema 1.1 validator");
241             return createInstance("org.apache.xerces.jaxp.validation.XMLSchema11Factory");
242         }
243 
244         if (debug) debugPrintln("all things were tried, but none was found. bailing out.");
245         return null;
246     }
247 
248     /**
249      * <p>Creates an instance of the specified and returns it.</p>
250      *
251      * @param className
252      *      fully qualified class name to be instantiated.
253      *
254      * @return null
255      *      if it fails. Error messages will be printed by this method.
256      */
createInstance( String className )257     SchemaFactory createInstance( String className ) {
258         try {
259             if (debug) debugPrintln("instantiating "+className);
260             Class clazz;
261             if( classLoader!=null )
262                 clazz = classLoader.loadClass(className);
263             else
264                 clazz = Class.forName(className);
265             if(debug)       debugPrintln("loaded it from "+which(clazz));
266             Object o = clazz.newInstance();
267 
268             if( o instanceof SchemaFactory )
269                 return (SchemaFactory)o;
270 
271             if (debug) debugPrintln(className+" is not assignable to "+SERVICE_CLASS.getName());
272         }
273         // The VM ran out of memory or there was some other serious problem. Re-throw.
274         catch (VirtualMachineError vme) {
275             throw vme;
276         }
277         // ThreadDeath should always be re-thrown
278         catch (ThreadDeath td) {
279             throw td;
280         }
281         catch (Throwable t) {
282             debugPrintln("failed to instantiate "+className);
283             if(debug)   t.printStackTrace();
284         }
285         return null;
286     }
287 
288     /**
289      * Returns an {@link Iterator} that enumerates all
290      * the META-INF/services files that we care.
291      */
createServiceFileIterator()292     private Iterable<URL> createServiceFileIterator() {
293         if (classLoader == null) {
294             ClassLoader classLoader = SchemaFactoryFinder.class.getClassLoader();
295             return Collections.singleton(classLoader.getResource(SERVICE_ID));
296         } else {
297             try {
298                 Enumeration<URL> e = classLoader.getResources(SERVICE_ID);
299                 if (debug && !e.hasMoreElements()) {
300                     debugPrintln("no "+SERVICE_ID+" file was found");
301                 }
302 
303                 // wrap it into an Iterator.
304                 return Collections.list(e);
305             } catch (IOException e) {
306                 if (debug) {
307                     debugPrintln("failed to enumerate resources "+SERVICE_ID);
308                     e.printStackTrace();
309                 }
310                 return Collections.emptySet();
311             }
312         }
313     }
314 
315     /** Searches for a SchemaFactory for a given schema language in a META-INF/services file. */
loadFromServicesFile(String schemaLanguage, String resourceName, InputStream in)316     private SchemaFactory loadFromServicesFile(String schemaLanguage, String resourceName, InputStream in) {
317 
318         if (debug) debugPrintln("Reading "+resourceName );
319 
320         // Read the service provider name in UTF-8 as specified in
321         // the jar spec.  Unfortunately this fails in Microsoft
322         // VJ++, which does not implement the UTF-8
323         // encoding. Theoretically, we should simply let it fail in
324         // that case, since the JVM is obviously broken if it
325         // doesn't support such a basic standard.  But since there
326         // are still some users attempting to use VJ++ for
327         // development, we have dropped in a fallback which makes a
328         // second attempt using the platform's default encoding. In
329         // VJ++ this is apparently ASCII, which is a subset of
330         // UTF-8... and since the strings we'll be reading here are
331         // also primarily limited to the 7-bit ASCII range (at
332         // least, in English versions), this should work well
333         // enough to keep us on the air until we're ready to
334         // officially decommit from VJ++. [Edited comment from
335         // jkesselm]
336         BufferedReader rd;
337         try {
338             rd = new BufferedReader(new InputStreamReader(in, "UTF-8"), DEFAULT_LINE_LENGTH);
339         } catch (java.io.UnsupportedEncodingException e) {
340             rd = new BufferedReader(new InputStreamReader(in), DEFAULT_LINE_LENGTH);
341         }
342 
343         String factoryClassName = null;
344         SchemaFactory resultFactory = null;
345         // See spec for provider-configuration files: http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider%20Configuration%20File
346         while (true) {
347             try {
348                 factoryClassName = rd.readLine();
349             } catch (IOException x) {
350                 // No provider found
351                 break;
352             }
353             if (factoryClassName != null) {
354                 // Ignore comments in the provider-configuration file
355                 int hashIndex = factoryClassName.indexOf('#');
356                 if (hashIndex != -1) {
357                     factoryClassName = factoryClassName.substring(0, hashIndex);
358                 }
359 
360                 // Ignore leading and trailing whitespace
361                 factoryClassName = factoryClassName.trim();
362 
363                 // If there's no text left or if this was a blank line, go to the next one.
364                 if (factoryClassName.length() == 0) {
365                     continue;
366                 }
367 
368                 try {
369                     // Found the right SchemaFactory if its isSchemaLanguageSupported(schemaLanguage) method returns true.
370                     SchemaFactory foundFactory = (SchemaFactory) createInstance(factoryClassName);
371                     if (foundFactory.isSchemaLanguageSupported(schemaLanguage)) {
372                         resultFactory = foundFactory;
373                         break;
374                     }
375                 }
376                 catch (Exception ignored) {}
377             }
378             else {
379                 break;
380             }
381         }
382 
383         IoUtils.closeQuietly(rd);
384 
385         return resultFactory;
386     }
387 
388     private static final Class SERVICE_CLASS = SchemaFactory.class;
389     private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName();
390 
which( Class clazz )391     private static String which( Class clazz ) {
392         return which( clazz.getName(), clazz.getClassLoader() );
393     }
394 
395     /**
396      * <p>Search the specified classloader for the given classname.</p>
397      *
398      * @param classname the fully qualified name of the class to search for
399      * @param loader the classloader to search
400      *
401      * @return the source location of the resource, or null if it wasn't found
402      */
which(String classname, ClassLoader loader)403     private static String which(String classname, ClassLoader loader) {
404         String classnameAsResource = classname.replace('.', '/') + ".class";
405 
406         if (loader == null)  loader = ClassLoader.getSystemClassLoader();
407 
408         URL it = loader.getResource(classnameAsResource);
409         return it != null ? it.toString() : null;
410     }
411 }
412