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