1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: ObjectFactory.java 468654 2006-10-28 07:09:23Z minchau $
20 */
21
22package org.apache.xml.serializer;
23
24import java.io.BufferedReader;
25import java.io.File;
26import java.io.FileInputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.InputStreamReader;
30import java.util.Properties;
31
32/**
33 * This class is duplicated for each JAXP subpackage so keep it in sync.
34 * It is package private and therefore is not exposed as part of the JAXP
35 * API.
36 * <p>
37 * This code is designed to implement the JAXP 1.1 spec pluggability
38 * feature and is designed to run on JDK version 1.1 and
39 * later, and to compile on JDK 1.2 and onward.
40 * The code also runs both as part of an unbundled jar file and
41 * when bundled as part of the JDK.
42 * <p>
43 * This class was moved from the <code>javax.xml.parsers.ObjectFactory</code>
44 * class and modified to be used as a general utility for creating objects
45 * dynamically.
46 *
47 * @xsl.usage internal
48 */
49class ObjectFactory {
50
51    //
52    // Constants
53    //
54
55    // name of default properties file to look for in JDK's jre/lib directory
56    private static final String DEFAULT_PROPERTIES_FILENAME =
57                                                     "xalan.properties";
58
59    private static final String SERVICES_PATH = "META-INF/services/";
60
61    /** Set to true for debugging */
62    private static final boolean DEBUG = false;
63
64    /** cache the contents of the xalan.properties file.
65     *  Until an attempt has been made to read this file, this will
66     * be null; if the file does not exist or we encounter some other error
67     * during the read, this will be empty.
68     */
69    private static Properties fXalanProperties = null;
70
71    /***
72     * Cache the time stamp of the xalan.properties file so
73     * that we know if it's been modified and can invalidate
74     * the cache when necessary.
75     */
76    private static long fLastModified = -1;
77
78    //
79    // Public static methods
80    //
81
82    /**
83     * Finds the implementation Class object in the specified order.  The
84     * specified order is the following:
85     * <ol>
86     *  <li>query the system property using <code>System.getProperty</code>
87     *  <li>read <code>META-INF/services/<i>factoryId</i></code> file
88     *  <li>use fallback classname
89     * </ol>
90     *
91     * @return instance of factory, never null
92     *
93     * @param factoryId             Name of the factory to find, same as
94     *                              a property name
95     * @param fallbackClassName     Implementation class name, if nothing else
96     *                              is found.  Use null to mean no fallback.
97     *
98     * @exception ObjectFactory.ConfigurationError
99     */
100    static Object createObject(String factoryId, String fallbackClassName)
101        throws ConfigurationError {
102        return createObject(factoryId, null, fallbackClassName);
103    } // createObject(String,String):Object
104
105    /**
106     * Finds the implementation Class object in the specified order.  The
107     * specified order is the following:
108     * <ol>
109     *  <li>query the system property using <code>System.getProperty</code>
110     *  <li>read <code>$java.home/lib/<i>propertiesFilename</i></code> file
111     *  <li>read <code>META-INF/services/<i>factoryId</i></code> file
112     *  <li>use fallback classname
113     * </ol>
114     *
115     * @return instance of factory, never null
116     *
117     * @param factoryId             Name of the factory to find, same as
118     *                              a property name
119     * @param propertiesFilename The filename in the $java.home/lib directory
120     *                           of the properties file.  If none specified,
121     *                           ${java.home}/lib/xalan.properties will be used.
122     * @param fallbackClassName     Implementation class name, if nothing else
123     *                              is found.  Use null to mean no fallback.
124     *
125     * @exception ObjectFactory.ConfigurationError
126     */
127    static Object createObject(String factoryId,
128                                      String propertiesFilename,
129                                      String fallbackClassName)
130        throws ConfigurationError
131    {
132        Class factoryClass = lookUpFactoryClass(factoryId,
133                                                propertiesFilename,
134                                                fallbackClassName);
135
136        if (factoryClass == null) {
137            throw new ConfigurationError(
138                "Provider for " + factoryId + " cannot be found", null);
139        }
140
141        try{
142            Object instance = factoryClass.newInstance();
143            debugPrintln("created new instance of factory " + factoryId);
144            return instance;
145        } catch (Exception x) {
146            throw new ConfigurationError(
147                "Provider for factory " + factoryId
148                    + " could not be instantiated: " + x, x);
149        }
150    } // createObject(String,String,String):Object
151
152    /**
153     * Finds the implementation Class object in the specified order.  The
154     * specified order is the following:
155     * <ol>
156     *  <li>query the system property using <code>System.getProperty</code>
157     *  <li>read <code>$java.home/lib/<i>propertiesFilename</i></code> file
158     *  <li>read <code>META-INF/services/<i>factoryId</i></code> file
159     *  <li>use fallback classname
160     * </ol>
161     *
162     * @return Class object of factory, never null
163     *
164     * @param factoryId             Name of the factory to find, same as
165     *                              a property name
166     * @param propertiesFilename The filename in the $java.home/lib directory
167     *                           of the properties file.  If none specified,
168     *                           ${java.home}/lib/xalan.properties will be used.
169     * @param fallbackClassName     Implementation class name, if nothing else
170     *                              is found.  Use null to mean no fallback.
171     *
172     * @exception ObjectFactory.ConfigurationError
173     */
174    static Class lookUpFactoryClass(String factoryId)
175        throws ConfigurationError
176    {
177        return lookUpFactoryClass(factoryId, null, null);
178    } // lookUpFactoryClass(String):Class
179
180    /**
181     * Finds the implementation Class object in the specified order.  The
182     * specified order is the following:
183     * <ol>
184     *  <li>query the system property using <code>System.getProperty</code>
185     *  <li>read <code>$java.home/lib/<i>propertiesFilename</i></code> file
186     *  <li>read <code>META-INF/services/<i>factoryId</i></code> file
187     *  <li>use fallback classname
188     * </ol>
189     *
190     * @return Class object that provides factory service, never null
191     *
192     * @param factoryId             Name of the factory to find, same as
193     *                              a property name
194     * @param propertiesFilename The filename in the $java.home/lib directory
195     *                           of the properties file.  If none specified,
196     *                           ${java.home}/lib/xalan.properties will be used.
197     * @param fallbackClassName     Implementation class name, if nothing else
198     *                              is found.  Use null to mean no fallback.
199     *
200     * @exception ObjectFactory.ConfigurationError
201     */
202    static Class lookUpFactoryClass(String factoryId,
203                                           String propertiesFilename,
204                                           String fallbackClassName)
205        throws ConfigurationError
206    {
207        String factoryClassName = lookUpFactoryClassName(factoryId,
208                                                         propertiesFilename,
209                                                         fallbackClassName);
210        ClassLoader cl = findClassLoader();
211
212        if (factoryClassName == null) {
213            factoryClassName = fallbackClassName;
214        }
215
216        // assert(className != null);
217        try{
218            Class providerClass = findProviderClass(factoryClassName,
219                                                    cl,
220                                                    true);
221            debugPrintln("created new instance of " + providerClass +
222                   " using ClassLoader: " + cl);
223            return providerClass;
224        } catch (ClassNotFoundException x) {
225            throw new ConfigurationError(
226                "Provider " + factoryClassName + " not found", x);
227        } catch (Exception x) {
228            throw new ConfigurationError(
229                "Provider "+factoryClassName+" could not be instantiated: "+x,
230                x);
231        }
232    } // lookUpFactoryClass(String,String,String):Class
233
234    /**
235     * Finds the name of the required implementation class in the specified
236     * order.  The specified order is the following:
237     * <ol>
238     *  <li>query the system property using <code>System.getProperty</code>
239     *  <li>read <code>$java.home/lib/<i>propertiesFilename</i></code> file
240     *  <li>read <code>META-INF/services/<i>factoryId</i></code> file
241     *  <li>use fallback classname
242     * </ol>
243     *
244     * @return name of class that provides factory service, never null
245     *
246     * @param factoryId             Name of the factory to find, same as
247     *                              a property name
248     * @param propertiesFilename The filename in the $java.home/lib directory
249     *                           of the properties file.  If none specified,
250     *                           ${java.home}/lib/xalan.properties will be used.
251     * @param fallbackClassName     Implementation class name, if nothing else
252     *                              is found.  Use null to mean no fallback.
253     *
254     * @exception ObjectFactory.ConfigurationError
255     */
256    static String lookUpFactoryClassName(String factoryId,
257                                                String propertiesFilename,
258                                                String fallbackClassName)
259    {
260        SecuritySupport ss = SecuritySupport.getInstance();
261
262        // Use the system property first
263        try {
264            String systemProp = ss.getSystemProperty(factoryId);
265            if (systemProp != null) {
266                debugPrintln("found system property, value=" + systemProp);
267                return systemProp;
268            }
269        } catch (SecurityException se) {
270            // Ignore and continue w/ next location
271        }
272
273        // Try to read from propertiesFilename, or
274        // $java.home/lib/xalan.properties
275        String factoryClassName = null;
276        // no properties file name specified; use
277        // $JAVA_HOME/lib/xalan.properties:
278        if (propertiesFilename == null) {
279            File propertiesFile = null;
280            boolean propertiesFileExists = false;
281            try {
282                String javah = ss.getSystemProperty("java.home");
283                propertiesFilename = javah + File.separator +
284                    "lib" + File.separator + DEFAULT_PROPERTIES_FILENAME;
285                propertiesFile = new File(propertiesFilename);
286                propertiesFileExists = ss.getFileExists(propertiesFile);
287            } catch (SecurityException e) {
288                // try again...
289                fLastModified = -1;
290                fXalanProperties = null;
291            }
292
293            synchronized (ObjectFactory.class) {
294                boolean loadProperties = false;
295                FileInputStream fis = null;
296                try {
297                    // file existed last time
298                    if(fLastModified >= 0) {
299                        if(propertiesFileExists &&
300                                (fLastModified < (fLastModified = ss.getLastModified(propertiesFile)))) {
301                            loadProperties = true;
302                        } else {
303                            // file has stopped existing...
304                            if(!propertiesFileExists) {
305                                fLastModified = -1;
306                                fXalanProperties = null;
307                            } // else, file wasn't modified!
308                        }
309                    } else {
310                        // file has started to exist:
311                        if(propertiesFileExists) {
312                            loadProperties = true;
313                            fLastModified = ss.getLastModified(propertiesFile);
314                        } // else, nothing's changed
315                    }
316                    if(loadProperties) {
317                        // must never have attempted to read xalan.properties
318                        // before (or it's outdeated)
319                        fXalanProperties = new Properties();
320                        fis = ss.getFileInputStream(propertiesFile);
321                        fXalanProperties.load(fis);
322                    }
323	        } catch (Exception x) {
324	            fXalanProperties = null;
325	            fLastModified = -1;
326                    // assert(x instanceof FileNotFoundException
327	            //        || x instanceof SecurityException)
328	            // In both cases, ignore and continue w/ next location
329	        }
330                finally {
331                    // try to close the input stream if one was opened.
332                    if (fis != null) {
333                        try {
334                            fis.close();
335                        }
336                        // Ignore the exception.
337                        catch (IOException exc) {}
338                    }
339                }
340            }
341            if(fXalanProperties != null) {
342                factoryClassName = fXalanProperties.getProperty(factoryId);
343            }
344        } else {
345            FileInputStream fis = null;
346            try {
347                fis = ss.getFileInputStream(new File(propertiesFilename));
348                Properties props = new Properties();
349                props.load(fis);
350                factoryClassName = props.getProperty(factoryId);
351            } catch (Exception x) {
352                // assert(x instanceof FileNotFoundException
353                //        || x instanceof SecurityException)
354                // In both cases, ignore and continue w/ next location
355            }
356            finally {
357                // try to close the input stream if one was opened.
358                if (fis != null) {
359                    try {
360                        fis.close();
361                    }
362                    // Ignore the exception.
363                    catch (IOException exc) {}
364                }
365            }
366        }
367        if (factoryClassName != null) {
368            debugPrintln("found in " + propertiesFilename + ", value="
369                          + factoryClassName);
370            return factoryClassName;
371        }
372
373        // Try Jar Service Provider Mechanism
374        return findJarServiceProviderName(factoryId);
375    } // lookUpFactoryClass(String,String):String
376
377    //
378    // Private static methods
379    //
380
381    /** Prints a message to standard error if debugging is enabled. */
382    private static void debugPrintln(String msg) {
383        if (DEBUG) {
384            System.err.println("JAXP: " + msg);
385        }
386    } // debugPrintln(String)
387
388    /**
389     * Figure out which ClassLoader to use.  For JDK 1.2 and later use
390     * the context ClassLoader.
391     */
392    static ClassLoader findClassLoader()
393        throws ConfigurationError
394    {
395        SecuritySupport ss = SecuritySupport.getInstance();
396
397        // Figure out which ClassLoader to use for loading the provider
398        // class.  If there is a Context ClassLoader then use it.
399        ClassLoader context = ss.getContextClassLoader();
400        ClassLoader system = ss.getSystemClassLoader();
401
402        ClassLoader chain = system;
403        while (true) {
404            if (context == chain) {
405                // Assert: we are on JDK 1.1 or we have no Context ClassLoader
406                // or any Context ClassLoader in chain of system classloader
407                // (including extension ClassLoader) so extend to widest
408                // ClassLoader (always look in system ClassLoader if Xalan
409                // is in boot/extension/system classpath and in current
410                // ClassLoader otherwise); normal classloaders delegate
411                // back to system ClassLoader first so this widening doesn't
412                // change the fact that context ClassLoader will be consulted
413                ClassLoader current = ObjectFactory.class.getClassLoader();
414
415                chain = system;
416                while (true) {
417                    if (current == chain) {
418                        // Assert: Current ClassLoader in chain of
419                        // boot/extension/system ClassLoaders
420                        return system;
421                    }
422                    if (chain == null) {
423                        break;
424                    }
425                    chain = ss.getParentClassLoader(chain);
426                }
427
428                // Assert: Current ClassLoader not in chain of
429                // boot/extension/system ClassLoaders
430                return current;
431            }
432
433            if (chain == null) {
434                // boot ClassLoader reached
435                break;
436            }
437
438            // Check for any extension ClassLoaders in chain up to
439            // boot ClassLoader
440            chain = ss.getParentClassLoader(chain);
441        };
442
443        // Assert: Context ClassLoader not in chain of
444        // boot/extension/system ClassLoaders
445        return context;
446    } // findClassLoader():ClassLoader
447
448    /**
449     * Create an instance of a class using the specified ClassLoader
450     */
451    static Object newInstance(String className, ClassLoader cl,
452                                      boolean doFallback)
453        throws ConfigurationError
454    {
455        // assert(className != null);
456        try{
457            Class providerClass = findProviderClass(className, cl, doFallback);
458            Object instance = providerClass.newInstance();
459            debugPrintln("created new instance of " + providerClass +
460                   " using ClassLoader: " + cl);
461            return instance;
462        } catch (ClassNotFoundException x) {
463            throw new ConfigurationError(
464                "Provider " + className + " not found", x);
465        } catch (Exception x) {
466            throw new ConfigurationError(
467                "Provider " + className + " could not be instantiated: " + x,
468                x);
469        }
470    }
471
472    /**
473     * Find a Class using the specified ClassLoader
474     */
475    static Class findProviderClass(String className, ClassLoader cl,
476                                           boolean doFallback)
477        throws ClassNotFoundException, ConfigurationError
478    {
479        //throw security exception if the calling thread is not allowed to access the
480        //class. Restrict the access to the package classes as specified in java.security policy.
481        SecurityManager security = System.getSecurityManager();
482        try{
483                if (security != null){
484                    final int lastDot = className.lastIndexOf(".");
485                    String packageName = className;
486                    if (lastDot != -1) packageName = className.substring(0, lastDot);
487                    security.checkPackageAccess(packageName);
488                 }
489        }catch(SecurityException e){
490            throw e;
491        }
492
493        Class providerClass;
494        if (cl == null) {
495            // XXX Use the bootstrap ClassLoader.  There is no way to
496            // load a class using the bootstrap ClassLoader that works
497            // in both JDK 1.1 and Java 2.  However, this should still
498            // work b/c the following should be true:
499            //
500            // (cl == null) iff current ClassLoader == null
501            //
502            // Thus Class.forName(String) will use the current
503            // ClassLoader which will be the bootstrap ClassLoader.
504            providerClass = Class.forName(className);
505        } else {
506            try {
507                providerClass = cl.loadClass(className);
508            } catch (ClassNotFoundException x) {
509                if (doFallback) {
510                    // Fall back to current classloader
511                    ClassLoader current = ObjectFactory.class.getClassLoader();
512                    if (current == null) {
513                        providerClass = Class.forName(className);
514                    } else if (cl != current) {
515                        cl = current;
516                        providerClass = cl.loadClass(className);
517                    } else {
518                        throw x;
519                    }
520                } else {
521                    throw x;
522                }
523            }
524        }
525
526        return providerClass;
527    }
528
529    /**
530     * Find the name of service provider using Jar Service Provider Mechanism
531     *
532     * @return instance of provider class if found or null
533     */
534    private static String findJarServiceProviderName(String factoryId)
535    {
536        SecuritySupport ss = SecuritySupport.getInstance();
537        String serviceId = SERVICES_PATH + factoryId;
538        InputStream is = null;
539
540        // First try the Context ClassLoader
541        ClassLoader cl = findClassLoader();
542
543        is = ss.getResourceAsStream(cl, serviceId);
544
545        // If no provider found then try the current ClassLoader
546        if (is == null) {
547            ClassLoader current = ObjectFactory.class.getClassLoader();
548            if (cl != current) {
549                cl = current;
550                is = ss.getResourceAsStream(cl, serviceId);
551            }
552        }
553
554        if (is == null) {
555            // No provider found
556            return null;
557        }
558
559        debugPrintln("found jar resource=" + serviceId +
560               " using ClassLoader: " + cl);
561
562        // Read the service provider name in UTF-8 as specified in
563        // the jar spec.  Unfortunately this fails in Microsoft
564        // VJ++, which does not implement the UTF-8
565        // encoding. Theoretically, we should simply let it fail in
566        // that case, since the JVM is obviously broken if it
567        // doesn't support such a basic standard.  But since there
568        // are still some users attempting to use VJ++ for
569        // development, we have dropped in a fallback which makes a
570        // second attempt using the platform's default encoding. In
571        // VJ++ this is apparently ASCII, which is a subset of
572        // UTF-8... and since the strings we'll be reading here are
573        // also primarily limited to the 7-bit ASCII range (at
574        // least, in English versions), this should work well
575        // enough to keep us on the air until we're ready to
576        // officially decommit from VJ++. [Edited comment from
577        // jkesselm]
578        BufferedReader rd;
579        try {
580            rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
581        } catch (java.io.UnsupportedEncodingException e) {
582            rd = new BufferedReader(new InputStreamReader(is));
583        }
584
585        String factoryClassName = null;
586        try {
587            // XXX Does not handle all possible input as specified by the
588            // Jar Service Provider specification
589            factoryClassName = rd.readLine();
590        } catch (IOException x) {
591            // No provider found
592            return null;
593        }
594        finally {
595            try {
596                // try to close the reader.
597                rd.close();
598            }
599            // Ignore the exception.
600            catch (IOException exc) {}
601        }
602
603        if (factoryClassName != null &&
604            ! "".equals(factoryClassName)) {
605            debugPrintln("found in resource, value="
606                   + factoryClassName);
607
608            // Note: here we do not want to fall back to the current
609            // ClassLoader because we want to avoid the case where the
610            // resource file was found using one ClassLoader and the
611            // provider class was instantiated using a different one.
612            return factoryClassName;
613        }
614
615        // No provider found
616        return null;
617    }
618
619    //
620    // Classes
621    //
622
623    /**
624     * A configuration error.
625     */
626    static class ConfigurationError
627        extends Error {
628                static final long serialVersionUID = 8859254254255146542L;
629        //
630        // Data
631        //
632
633        /** Exception. */
634        private Exception exception;
635
636        //
637        // Constructors
638        //
639
640        /**
641         * Construct a new instance with the specified detail string and
642         * exception.
643         */
644        ConfigurationError(String msg, Exception x) {
645            super(msg);
646            this.exception = x;
647        } // <init>(String,Exception)
648
649        //
650        // Public methods
651        //
652
653        /** Returns the exception associated to this error. */
654        Exception getException() {
655            return exception;
656        } // getException():Exception
657
658    } // class ConfigurationError
659
660} // class ObjectFactory
661