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