001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: ObjectFactory.java 1225583 2011-12-29 16:08:00Z mrglavas $
020     */
021    
022    package org.apache.xalan.lib;
023    
024    import java.io.InputStream;
025    import java.io.IOException;
026    import java.io.File;
027    import java.io.FileInputStream;
028    
029    import java.util.Properties;
030    import java.io.BufferedReader;
031    import java.io.InputStreamReader;
032    
033    /**
034     * This class is duplicated for each JAXP subpackage so keep it in sync.
035     * It is package private and therefore is not exposed as part of the JAXP
036     * API.
037     * <p>
038     * This code is designed to implement the JAXP 1.1 spec pluggability
039     * feature and is designed to run on JDK version 1.1 and
040     * later, and to compile on JDK 1.2 and onward.  
041     * The code also runs both as part of an unbundled jar file and
042     * when bundled as part of the JDK.
043     * <p>
044     * This class was moved from the <code>javax.xml.parsers.ObjectFactory</code>
045     * class and modified to be used as a general utility for creating objects 
046     * dynamically.
047     *
048     * @version $Id: ObjectFactory.java 1225583 2011-12-29 16:08:00Z mrglavas $
049     */
050    final class ObjectFactory {
051    
052        //
053        // Constants
054        //
055    
056        // name of default properties file to look for in JDK's jre/lib directory
057        private static final String DEFAULT_PROPERTIES_FILENAME =
058                                                         "xalan.properties";
059    
060        private static final String SERVICES_PATH = "META-INF/services/";
061    
062        /** Set to true for debugging */
063        private static final boolean DEBUG = false;
064    
065        /** cache the contents of the xalan.properties file.
066         *  Until an attempt has been made to read this file, this will
067         * be null; if the file does not exist or we encounter some other error
068         * during the read, this will be empty.
069         */
070        private static Properties fXalanProperties = null;
071    
072        /***
073         * Cache the time stamp of the xalan.properties file so
074         * that we know if it's been modified and can invalidate
075         * the cache when necessary.
076         */
077        private static long fLastModified = -1;
078    
079        //
080        // Public static methods
081        //
082    
083        /**
084         * Finds the implementation Class object in the specified order.  The
085         * specified order is the following:
086         * <ol>
087         *  <li>query the system property using <code>System.getProperty</code>
088         *  <li>read <code>META-INF/services/<i>factoryId</i></code> file
089         *  <li>use fallback classname
090         * </ol>
091         *
092         * @return instance of factory, never null
093         *
094         * @param factoryId             Name of the factory to find, same as
095         *                              a property name
096         * @param fallbackClassName     Implementation class name, if nothing else
097         *                              is found.  Use null to mean no fallback.
098         *
099         * @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            // Use the system property first
262            try {
263                String systemProp = SecuritySupport.getSystemProperty(factoryId);
264                if (systemProp != null) {
265                    debugPrintln("found system property, value=" + systemProp);
266                    return systemProp;
267                }
268            } catch (SecurityException se) {
269                // Ignore and continue w/ next location
270            }
271    
272            // Try to read from propertiesFilename, or
273            // $java.home/lib/xalan.properties
274            String factoryClassName = null;
275            // no properties file name specified; use
276            // $JAVA_HOME/lib/xalan.properties:
277            if (propertiesFilename == null) {
278                File propertiesFile = null;
279                boolean propertiesFileExists = false;
280                try {
281                    String javah = SecuritySupport.getSystemProperty("java.home");
282                    propertiesFilename = javah + File.separator +
283                        "lib" + File.separator + DEFAULT_PROPERTIES_FILENAME;
284                    propertiesFile = new File(propertiesFilename);
285                    propertiesFileExists = SecuritySupport.getFileExists(propertiesFile);
286                } catch (SecurityException e) {
287                    // try again...
288                    fLastModified = -1;
289                    fXalanProperties = null;
290                }
291    
292                synchronized (ObjectFactory.class) {
293                    boolean loadProperties = false;
294                    FileInputStream fis = null;
295                    try {
296                        // file existed last time
297                        if(fLastModified >= 0) {
298                            if(propertiesFileExists &&
299                                    (fLastModified < (fLastModified = SecuritySupport.getLastModified(propertiesFile)))) {
300                                loadProperties = true;
301                            } else {
302                                // file has stopped existing...
303                                if(!propertiesFileExists) {
304                                    fLastModified = -1;
305                                    fXalanProperties = null;
306                                } // else, file wasn't modified!
307                            }
308                        } else {
309                            // file has started to exist:
310                            if(propertiesFileExists) {
311                                loadProperties = true;
312                                fLastModified = SecuritySupport.getLastModified(propertiesFile);
313                            } // else, nothing's changed
314                        }
315                        if(loadProperties) {
316                            // must never have attempted to read xalan.properties
317                            // before (or it's outdeated)
318                            fXalanProperties = new Properties();
319                            fis = SecuritySupport.getFileInputStream(propertiesFile);
320                            fXalanProperties.load(fis);
321                        }
322                    } catch (Exception x) {
323                        fXalanProperties = null;
324                        fLastModified = -1;
325                        // assert(x instanceof FileNotFoundException
326                        //        || x instanceof SecurityException)
327                        // In both cases, ignore and continue w/ next location
328                    }
329                    finally {
330                        // try to close the input stream if one was opened.
331                        if (fis != null) {
332                            try {
333                                fis.close();
334                            }
335                            // Ignore the exception.
336                            catch (IOException exc) {}
337                        }
338                    }                   
339                }
340                if(fXalanProperties != null) {
341                    factoryClassName = fXalanProperties.getProperty(factoryId);
342                }
343            } else {
344                FileInputStream fis = null;
345                try {
346                    fis = SecuritySupport.getFileInputStream(new File(propertiesFilename));
347                    Properties props = new Properties();
348                    props.load(fis);
349                    factoryClassName = props.getProperty(factoryId);
350                } catch (Exception x) {
351                    // assert(x instanceof FileNotFoundException
352                    //        || x instanceof SecurityException)
353                    // In both cases, ignore and continue w/ next location
354                }
355                finally {
356                    // try to close the input stream if one was opened.
357                    if (fis != null) {
358                        try {
359                            fis.close();
360                        }
361                        // Ignore the exception.
362                        catch (IOException exc) {}
363                    }
364                }               
365            }
366            if (factoryClassName != null) {
367                debugPrintln("found in " + propertiesFilename + ", value="
368                              + factoryClassName);
369                return factoryClassName;
370            }
371    
372            // Try Jar Service Provider Mechanism
373            return findJarServiceProviderName(factoryId);
374        } // lookUpFactoryClass(String,String):String
375    
376        //
377        // Private static methods
378        //
379    
380        /** Prints a message to standard error if debugging is enabled. */
381        private static void debugPrintln(String msg) {
382            if (DEBUG) {
383                System.err.println("JAXP: " + msg);
384            }
385        } // debugPrintln(String)
386    
387        /**
388         * Figure out which ClassLoader to use.  For JDK 1.2 and later use
389         * the context ClassLoader.
390         */
391        static ClassLoader findClassLoader()
392            throws ConfigurationError
393        { 
394            // Figure out which ClassLoader to use for loading the provider
395            // class.  If there is a Context ClassLoader then use it.
396            ClassLoader context = SecuritySupport.getContextClassLoader();
397            ClassLoader system = SecuritySupport.getSystemClassLoader();
398    
399            ClassLoader chain = system;
400            while (true) {
401                if (context == chain) {
402                    // Assert: we are on JDK 1.1 or we have no Context ClassLoader
403                    // or any Context ClassLoader in chain of system classloader
404                    // (including extension ClassLoader) so extend to widest
405                    // ClassLoader (always look in system ClassLoader if Xalan
406                    // is in boot/extension/system classpath and in current
407                    // ClassLoader otherwise); normal classloaders delegate
408                    // back to system ClassLoader first so this widening doesn't
409                    // change the fact that context ClassLoader will be consulted
410                    ClassLoader current = ObjectFactory.class.getClassLoader();
411    
412                    chain = system;
413                    while (true) {
414                        if (current == chain) {
415                            // Assert: Current ClassLoader in chain of
416                            // boot/extension/system ClassLoaders
417                            return system;
418                        }
419                        if (chain == null) {
420                            break;
421                        }
422                        chain = SecuritySupport.getParentClassLoader(chain);
423                    }
424    
425                    // Assert: Current ClassLoader not in chain of
426                    // boot/extension/system ClassLoaders
427                    return current;
428                }
429    
430                if (chain == null) {
431                    // boot ClassLoader reached
432                    break;
433                }
434    
435                // Check for any extension ClassLoaders in chain up to
436                // boot ClassLoader
437                chain = SecuritySupport.getParentClassLoader(chain);
438            };
439    
440            // Assert: Context ClassLoader not in chain of
441            // boot/extension/system ClassLoaders
442            return context;
443        } // findClassLoader():ClassLoader
444    
445        /**
446         * Create an instance of a class using the specified ClassLoader
447         */ 
448        static Object newInstance(String className, ClassLoader cl,
449                                          boolean doFallback)
450            throws ConfigurationError
451        {
452            // assert(className != null);
453            try{
454                Class providerClass = findProviderClass(className, cl, doFallback);
455                Object instance = providerClass.newInstance();
456                debugPrintln("created new instance of " + providerClass +
457                       " using ClassLoader: " + cl);
458                return instance;
459            } catch (ClassNotFoundException x) {
460                throw new ConfigurationError(
461                    "Provider " + className + " not found", x);
462            } catch (Exception x) {
463                throw new ConfigurationError(
464                    "Provider " + className + " could not be instantiated: " + x,
465                    x);
466            }
467        }
468    
469        /**
470         * Find a Class using the specified ClassLoader
471         */ 
472        static Class findProviderClass(String className, ClassLoader cl,
473                                               boolean doFallback)
474            throws ClassNotFoundException, ConfigurationError
475        {   
476            //throw security exception if the calling thread is not allowed to access the
477            //class. Restrict the access to the package classes as specified in java.security policy.
478            SecurityManager security = System.getSecurityManager();
479            try{
480                    if (security != null){
481                        final int lastDot = className.lastIndexOf('.');
482                        String packageName = className;
483                        if (lastDot != -1) packageName = className.substring(0, lastDot);
484                        security.checkPackageAccess(packageName);
485                     }   
486            }catch(SecurityException e){
487                throw e;
488            }
489            
490            Class providerClass;
491            if (cl == null) {
492                // XXX Use the bootstrap ClassLoader.  There is no way to
493                // load a class using the bootstrap ClassLoader that works
494                // in both JDK 1.1 and Java 2.  However, this should still
495                // work b/c the following should be true:
496                //
497                // (cl == null) iff current ClassLoader == null
498                //
499                // Thus Class.forName(String) will use the current
500                // ClassLoader which will be the bootstrap ClassLoader.
501                providerClass = Class.forName(className);
502            } else {
503                try {
504                    providerClass = cl.loadClass(className);
505                } catch (ClassNotFoundException x) {
506                    if (doFallback) {
507                        // Fall back to current classloader
508                        ClassLoader current = ObjectFactory.class.getClassLoader();
509                        if (current == null) {
510                            providerClass = Class.forName(className);
511                        } else if (cl != current) {
512                            cl = current;
513                            providerClass = cl.loadClass(className);
514                        } else {
515                            throw x;
516                        }
517                    } else {
518                        throw x;
519                    }
520                }
521            }
522    
523            return providerClass;
524        }
525    
526        /**
527         * Find the name of service provider using Jar Service Provider Mechanism
528         *
529         * @return instance of provider class if found or null
530         */
531        private static String findJarServiceProviderName(String factoryId)
532        {
533            String serviceId = SERVICES_PATH + factoryId;
534            InputStream is = null;
535    
536            // First try the Context ClassLoader
537            ClassLoader cl = findClassLoader();
538    
539            is = SecuritySupport.getResourceAsStream(cl, serviceId);
540    
541            // If no provider found then try the current ClassLoader
542            if (is == null) {
543                ClassLoader current = ObjectFactory.class.getClassLoader();
544                if (cl != current) {
545                    cl = current;
546                    is = SecuritySupport.getResourceAsStream(cl, serviceId);
547                }
548            }
549    
550            if (is == null) {
551                // No provider found
552                return null;
553            }
554    
555            debugPrintln("found jar resource=" + serviceId +
556                   " using ClassLoader: " + cl);
557    
558            // Read the service provider name in UTF-8 as specified in
559            // the jar spec.  Unfortunately this fails in Microsoft
560            // VJ++, which does not implement the UTF-8
561            // encoding. Theoretically, we should simply let it fail in
562            // that case, since the JVM is obviously broken if it
563            // doesn't support such a basic standard.  But since there
564            // are still some users attempting to use VJ++ for
565            // development, we have dropped in a fallback which makes a
566            // second attempt using the platform's default encoding. In
567            // VJ++ this is apparently ASCII, which is a subset of
568            // UTF-8... and since the strings we'll be reading here are
569            // also primarily limited to the 7-bit ASCII range (at
570            // least, in English versions), this should work well
571            // enough to keep us on the air until we're ready to
572            // officially decommit from VJ++. [Edited comment from
573            // jkesselm]
574            BufferedReader rd;
575            try {
576                rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
577            } catch (java.io.UnsupportedEncodingException e) {
578                rd = new BufferedReader(new InputStreamReader(is));
579            }
580            
581            String factoryClassName = null;
582            try {
583                // XXX Does not handle all possible input as specified by the
584                // Jar Service Provider specification
585                factoryClassName = rd.readLine();
586            } catch (IOException x) {
587                // No provider found
588                return null;
589            }
590            finally {
591                try {
592                    // try to close the reader.
593                    rd.close();
594                }
595                // Ignore the exception.
596                catch (IOException exc) {}
597            }          
598    
599            if (factoryClassName != null &&
600                ! "".equals(factoryClassName)) {
601                debugPrintln("found in resource, value="
602                       + factoryClassName);
603    
604                // Note: here we do not want to fall back to the current
605                // ClassLoader because we want to avoid the case where the
606                // resource file was found using one ClassLoader and the
607                // provider class was instantiated using a different one.
608                return factoryClassName;
609            }
610    
611            // No provider found
612            return null;
613        }
614    
615        //
616        // Classes
617        //
618    
619        /**
620         * A configuration error.
621         */
622        static class ConfigurationError 
623            extends Error {
624                    static final long serialVersionUID = -7640369932165775029L;
625            //
626            // Data
627            //
628    
629            /** Exception. */
630            private Exception exception;
631    
632            //
633            // Constructors
634            //
635    
636            /**
637             * Construct a new instance with the specified detail string and
638             * exception.
639             */
640            ConfigurationError(String msg, Exception x) {
641                super(msg);
642                this.exception = x;
643            } // <init>(String,Exception)
644    
645            //
646            // Public methods
647            //
648    
649            /** Returns the exception associated to this error. */
650            Exception getException() {
651                return exception;
652            } // getException():Exception
653    
654        } // class ConfigurationError
655    
656    } // class ObjectFactory