Home | History | Annotate | Download | only in functions
      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 
     22 package org.apache.xpath.functions;
     23 
     24 import java.io.InputStream;
     25 import java.io.IOException;
     26 import java.io.File;
     27 import java.io.FileInputStream;
     28 
     29 import java.util.Properties;
     30 import java.io.BufferedReader;
     31 import 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  */
     50 class 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