SchemaFactoryFinder.java revision 1056:f85154af719f
1/*
2 * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.xml.validation;
27
28import java.io.File;
29import java.lang.reflect.Method;
30import java.lang.reflect.Modifier;
31import java.security.AccessControlContext;
32import java.security.AccessController;
33import java.security.PrivilegedAction;
34import java.util.Properties;
35import java.util.ServiceConfigurationError;
36import java.util.ServiceLoader;
37import java.util.function.Supplier;
38
39/**
40 * Implementation of {@link SchemaFactory#newInstance(String)}.
41 *
42 * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
43 * @since 1.5
44 */
45class SchemaFactoryFinder  {
46
47    /** debug support code. */
48    private static boolean debug = false;
49    /**
50     *<p> Take care of restrictions imposed by java security model </p>
51     */
52    private static final SecuritySupport ss = new SecuritySupport();
53    private static final String DEFAULT_PACKAGE = "com.sun.org.apache.xerces.internal";
54    /**
55     * <p>Cache properties for performance.</p>
56     */
57    private static final Properties cacheProps = new Properties();
58
59    /**
60     * <p>First time requires initialization overhead.</p>
61     */
62    private static volatile boolean firstTime = true;
63
64    static {
65        // Use try/catch block to support applets
66        try {
67            debug = ss.getSystemProperty("jaxp.debug") != null;
68        } catch (Exception unused) {
69            debug = false;
70        }
71    }
72
73    /**
74     * <p>Conditional debug printing.</p>
75     *
76     * @param msgGen Supplier function that returns debug message
77     */
78    private static void debugPrintln(Supplier<String> msgGen) {
79        if (debug) {
80            System.err.println("JAXP: " + msgGen.get());
81        }
82    }
83
84    /**
85     * <p><code>ClassLoader</code> to use to find <code>SchemaFactory</code>.</p>
86     */
87    private final ClassLoader classLoader;
88
89    /**
90     * <p>Constructor that specifies <code>ClassLoader</code> to use
91     * to find <code>SchemaFactory</code>.</p>
92     *
93     * @param loader
94     *      to be used to load resource, {@link SchemaFactory}, and
95     *      {@link SchemaFactoryLoader} implementations during
96     *      the resolution process.
97     *      If this parameter is null, the default system class loader
98     *      will be used.
99     */
100    public SchemaFactoryFinder(ClassLoader loader) {
101        this.classLoader = loader;
102        if( debug ) {
103            debugDisplayClassLoader();
104        }
105    }
106
107    private void debugDisplayClassLoader() {
108        try {
109            if( classLoader == ss.getContextClassLoader() ) {
110                debugPrintln(()->"using thread context class loader ("+classLoader+") for search");
111                return;
112            }
113        } catch( Throwable unused ) {
114            // getContextClassLoader() undefined in JDK1.1
115        }
116
117        if( classLoader==ClassLoader.getSystemClassLoader() ) {
118            debugPrintln(()->"using system class loader ("+classLoader+") for search");
119            return;
120        }
121
122        debugPrintln(()->"using class loader ("+classLoader+") for search");
123    }
124
125    /**
126     * <p>Creates a new {@link SchemaFactory} object for the specified
127     * schema language.</p>
128     *
129     * @param schemaLanguage
130     *      See {@link SchemaFactory Schema Language} table in <code>SchemaFactory</code>
131     *      for the list of available schema languages.
132     *
133     * @return <code>null</code> if the callee fails to create one.
134     *
135     * @throws NullPointerException
136     *      If the <code>schemaLanguage</code> parameter is null.
137     * @throws SchemaFactoryConfigurationError
138     *      If a configuration error is encountered.
139     */
140    public SchemaFactory newFactory(String schemaLanguage) {
141        if(schemaLanguage==null) {
142            throw new NullPointerException();
143        }
144        SchemaFactory f = _newFactory(schemaLanguage);
145        if (f != null) {
146            debugPrintln(()->"factory '" + f.getClass().getName() + "' was found for " + schemaLanguage);
147        } else {
148            debugPrintln(()->"unable to find a factory for " + schemaLanguage);
149        }
150        return f;
151    }
152
153    /**
154     * <p>Lookup a <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.</p>
155     *
156     * @param schemaLanguage Schema language to lookup <code>SchemaFactory</code> for.
157     *
158     * @return <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.
159     */
160    private SchemaFactory _newFactory(String schemaLanguage) {
161        SchemaFactory sf;
162
163        String propertyName = SERVICE_CLASS.getName() + ":" + schemaLanguage;
164
165        // system property look up
166        try {
167            debugPrintln(()->"Looking up system property '"+propertyName+"'" );
168            String r = ss.getSystemProperty(propertyName);
169            if(r!=null) {
170                debugPrintln(()->"The value is '"+r+"'");
171                sf = createInstance(r, true);
172                if(sf!=null)    return sf;
173            } else
174                debugPrintln(()->"The property is undefined.");
175        } catch( Throwable t ) {
176            if( debug ) {
177                debugPrintln(()->"failed to look up system property '"+propertyName+"'" );
178                t.printStackTrace();
179            }
180        }
181
182        String javah = ss.getSystemProperty( "java.home" );
183        String configFile = javah + File.separator +
184        "conf" + File.separator + "jaxp.properties";
185
186
187        // try to read from $java.home/conf/jaxp.properties
188        try {
189            if(firstTime){
190                synchronized(cacheProps){
191                    if(firstTime){
192                        File f=new File( configFile );
193                        firstTime = false;
194                        if(ss.doesFileExist(f)){
195                            debugPrintln(()->"Read properties file " + f);
196                            cacheProps.load(ss.getFileInputStream(f));
197                        }
198                    }
199                }
200            }
201            final String factoryClassName = cacheProps.getProperty(propertyName);
202            debugPrintln(()->"found " + factoryClassName + " in $java.home/conf/jaxp.properties");
203
204            if (factoryClassName != null) {
205                sf = createInstance(factoryClassName, true);
206                if(sf != null){
207                    return sf;
208                }
209            }
210        } catch (Exception ex) {
211            if (debug) {
212                ex.printStackTrace();
213            }
214        }
215
216        // Try with ServiceLoader
217        final SchemaFactory factoryImpl = findServiceProvider(schemaLanguage);
218
219        // The following assertion should always be true.
220        // Uncomment it, recompile, and run with -ea in case of doubts:
221        // assert factoryImpl == null || factoryImpl.isSchemaLanguageSupported(schemaLanguage);
222
223        if (factoryImpl != null) {
224            return factoryImpl;
225        }
226
227        // platform default
228        if(schemaLanguage.equals("http://www.w3.org/2001/XMLSchema")) {
229            debugPrintln(()->"attempting to use the platform default XML Schema validator");
230            return createInstance("com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory", true);
231        }
232
233        debugPrintln(()->"all things were tried, but none was found. bailing out.");
234        return null;
235    }
236
237    /** <p>Create class using appropriate ClassLoader.</p>
238     *
239     * @param className Name of class to create.
240     * @return Created class or <code>null</code>.
241     */
242    private Class<?> createClass(String className) {
243        Class<?> clazz;
244        // make sure we have access to restricted packages
245        boolean internal = false;
246        if (System.getSecurityManager() != null) {
247            if (className != null && className.startsWith(DEFAULT_PACKAGE)) {
248                internal = true;
249            }
250        }
251
252        try {
253            if (classLoader != null && !internal) {
254                clazz = Class.forName(className, false, classLoader);
255            } else {
256                clazz = Class.forName(className);
257            }
258        } catch (Throwable t) {
259            if(debug)  {
260                t.printStackTrace();
261            }
262            return null;
263        }
264
265        return clazz;
266    }
267
268    /**
269     * <p>Creates an instance of the specified and returns it.</p>
270     *
271     * @param className
272     *      fully qualified class name to be instantiated.
273     *
274     * @return null
275     *      if it fails. Error messages will be printed by this method.
276     */
277    SchemaFactory createInstance( String className ) {
278        return createInstance( className, false );
279    }
280
281    SchemaFactory createInstance( String className, boolean useServicesMechanism ) {
282        SchemaFactory schemaFactory = null;
283
284        debugPrintln(()->"createInstance(" + className + ")");
285
286        // get Class from className
287        Class<?> clazz = createClass(className);
288        if (clazz == null) {
289                debugPrintln(()->"failed to getClass(" + className + ")");
290                return null;
291        }
292        debugPrintln(()->"loaded " + className + " from " + which(clazz));
293
294        // instantiate Class as a SchemaFactory
295        try {
296                if (!SchemaFactory.class.isAssignableFrom(clazz)) {
297                    throw new ClassCastException(clazz.getName()
298                                + " cannot be cast to " + SchemaFactory.class);
299                }
300                if (!useServicesMechanism) {
301                    schemaFactory = newInstanceNoServiceLoader(clazz);
302                }
303                if (schemaFactory == null) {
304                    schemaFactory = (SchemaFactory) clazz.newInstance();
305                }
306        } catch (ClassCastException classCastException) {
307                debugPrintln(()->"could not instantiate " + clazz.getName());
308                if (debug) {
309                        classCastException.printStackTrace();
310                }
311                return null;
312        } catch (IllegalAccessException illegalAccessException) {
313                debugPrintln(()->"could not instantiate " + clazz.getName());
314                if (debug) {
315                        illegalAccessException.printStackTrace();
316                }
317                return null;
318        } catch (InstantiationException instantiationException) {
319                debugPrintln(()->"could not instantiate " + clazz.getName());
320                if (debug) {
321                        instantiationException.printStackTrace();
322                }
323                return null;
324        }
325
326        return schemaFactory;
327    }
328
329    /**
330     * Try to construct using newXMLSchemaFactoryNoServiceLoader
331     *   method if available.
332     */
333    private static SchemaFactory newInstanceNoServiceLoader(
334         Class<?> providerClass
335    ) {
336        // Retain maximum compatibility if no security manager.
337        if (System.getSecurityManager() == null) {
338            return null;
339        }
340        try {
341            final Method creationMethod =
342                providerClass.getDeclaredMethod(
343                    "newXMLSchemaFactoryNoServiceLoader"
344                );
345            final int modifiers = creationMethod.getModifiers();
346
347            // Do not call the method if it's not public static.
348            if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
349                return null;
350            }
351
352            // Only calls "newXMLSchemaFactoryNoServiceLoader" if it's
353            // declared to return an instance of SchemaFactory.
354            final Class<?> returnType = creationMethod.getReturnType();
355            if (SERVICE_CLASS.isAssignableFrom(returnType)) {
356                return SERVICE_CLASS.cast(creationMethod.invoke(null, (Object[])null));
357            } else {
358                // Should not happen since
359                // XMLSchemaFactory.newXMLSchemaFactoryNoServiceLoader is
360                // declared to return XMLSchemaFactory.
361                throw new ClassCastException(returnType
362                            + " cannot be cast to " + SERVICE_CLASS);
363            }
364        } catch(ClassCastException e) {
365            throw new SchemaFactoryConfigurationError(e.getMessage(), e);
366        } catch (NoSuchMethodException exc) {
367            return null;
368        } catch (Exception exc) {
369            return null;
370        }
371    }
372
373    // Call isSchemaLanguageSupported with initial context.
374    private boolean isSchemaLanguageSupportedBy(final SchemaFactory factory,
375            final String schemaLanguage,
376            AccessControlContext acc) {
377        return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
378            public Boolean run() {
379                return factory.isSchemaLanguageSupported(schemaLanguage);
380            }
381        }, acc);
382    }
383
384    /**
385     * Finds a service provider subclass of SchemaFactory that supports the
386     * given schema language using the ServiceLoader.
387     *
388     * @param schemaLanguage The schema language for which we seek a factory.
389     * @return A SchemaFactory supporting the specified schema language, or null
390     *         if none is found.
391     * @throws SchemaFactoryConfigurationError if a configuration error is found.
392     */
393    private SchemaFactory findServiceProvider(final String schemaLanguage) {
394        assert schemaLanguage != null;
395        // store current context.
396        final AccessControlContext acc = AccessController.getContext();
397        try {
398            return AccessController.doPrivileged(new PrivilegedAction<SchemaFactory>() {
399                public SchemaFactory run() {
400                    final ServiceLoader<SchemaFactory> loader =
401                            ServiceLoader.load(SERVICE_CLASS);
402                    for (SchemaFactory factory : loader) {
403                        // restore initial context to call
404                        // factory.isSchemaLanguageSupported
405                        if (isSchemaLanguageSupportedBy(factory, schemaLanguage, acc)) {
406                            return factory;
407                        }
408                    }
409                    return null; // no factory found.
410                }
411            });
412        } catch (ServiceConfigurationError error) {
413            throw new SchemaFactoryConfigurationError(
414                    "Provider for " + SERVICE_CLASS + " cannot be created", error);
415        }
416    }
417
418    private static final Class<SchemaFactory> SERVICE_CLASS = SchemaFactory.class;
419
420
421    // Used for debugging purposes
422    private static String which( Class<?> clazz ) {
423        return ss.getClassSource(clazz);
424    }
425}
426