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