Home | History | Annotate | Download | only in datatype
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 // $Id: FactoryFinder.java 670432 2008-06-23 02:02:08Z mrglavas $
     19 
     20 package javax.xml.datatype;
     21 
     22 import java.io.BufferedReader;
     23 import java.io.File;
     24 import java.io.IOException;
     25 import java.io.InputStream;
     26 import java.io.InputStreamReader;
     27 import java.net.URL;
     28 import java.util.Properties;
     29 
     30 /**
     31  * <p>Implement pluggabile Datatypes.</p>
     32  *
     33  * <p>This class is duplicated for each JAXP subpackage so keep it in
     34  * sync.  It is package private for secure class loading.</p>
     35  *
     36  * @author <a href="mailto:Jeff.Suttor (at) Sun.com">Jeff Suttor</a>
     37  * @version $Revision: 670432 $, $Date: 2008-06-22 19:02:08 -0700 (Sun, 22 Jun 2008) $
     38  * @since 1.5
     39  */
     40 final class FactoryFinder {
     41 
     42     /**
     43      * <p>Name of class to display in output messages.</p>
     44      */
     45     private static final String CLASS_NAME = "javax.xml.datatype.FactoryFinder";
     46 
     47     /**
     48      * <p>Debug flag to trace loading process.</p>
     49      */
     50     private static boolean debug = false;
     51 
     52     /**
     53      * <p>Cache properties for performance.</p>
     54      */
     55     private static Properties cacheProps = new Properties();
     56 
     57     /**
     58      * <p>First time requires initialization overhead.</p>
     59      */
     60     private static boolean firstTime = true;
     61 
     62     /**
     63      * Default columns per line.
     64      */
     65     private static final int DEFAULT_LINE_LENGTH = 80;
     66 
     67     /**
     68      * <p>Check to see if debugging enabled by property.</p>
     69      *
     70      * <p>Use try/catch block to support applets, which throws
     71      * SecurityException out of this code.</p>
     72      *
     73      */
     74     static {
     75         try {
     76             String val = SecuritySupport.getSystemProperty("jaxp.debug");
     77             // Allow simply setting the prop to turn on debug
     78             debug = val != null && (! "false".equals(val));
     79         } catch (Exception x) {
     80             debug = false;
     81         }
     82     }
     83 
     84     private FactoryFinder() {}
     85 
     86     /**
     87      * <p>Output debugging messages.</p>
     88      *
     89      * @param msg <code>String</code> to print to <code>stderr</code>.
     90      */
     91     private static void debugPrintln(String msg) {
     92         if (debug) {
     93             System.err.println(
     94                 CLASS_NAME
     95                 + ":"
     96                 + msg);
     97         }
     98     }
     99 
    100     /**
    101      * <p>Find the appropriate <code>ClassLoader</code> to use.</p>
    102      *
    103      * <p>The context ClassLoader is prefered.</p>
    104      *
    105      * @return <code>ClassLoader</code> to use.
    106      *
    107      * @throws ConfigurationError If a valid <code>ClassLoader</code> cannot be identified.
    108      */
    109     private static ClassLoader findClassLoader()
    110         throws ConfigurationError {
    111         ClassLoader classLoader;
    112 
    113         // Figure out which ClassLoader to use for loading the provider
    114         // class.  If there is a Context ClassLoader then use it.
    115 
    116         classLoader = SecuritySupport.getContextClassLoader();
    117 
    118         if (debug) debugPrintln(
    119             "Using context class loader: "
    120             + classLoader);
    121 
    122         if (classLoader == null) {
    123             // if we have no Context ClassLoader
    124             // so use the current ClassLoader
    125             classLoader = FactoryFinder.class.getClassLoader();
    126             if (debug) debugPrintln(
    127                 "Using the class loader of FactoryFinder: "
    128                 + classLoader);
    129         }
    130 
    131         return classLoader;
    132     }
    133 
    134     /**
    135      * <p>Create an instance of a class using the specified ClassLoader.</p>
    136      *
    137      * @param className Name of class to create.
    138      * @param classLoader ClassLoader to use to create named class.
    139      *
    140      * @return New instance of specified class created using the specified ClassLoader.
    141      *
    142      * @throws ConfigurationError If class could not be created.
    143      */
    144     static Object newInstance(
    145         String className,
    146         ClassLoader classLoader)
    147         throws ConfigurationError {
    148 
    149         try {
    150             Class spiClass;
    151             if (classLoader == null) {
    152                 spiClass = Class.forName(className);
    153             } else {
    154                 spiClass = classLoader.loadClass(className);
    155             }
    156 
    157             if (debug) {
    158                 debugPrintln("Loaded " + className + " from " + which(spiClass));
    159             }
    160 
    161             return spiClass.newInstance();
    162         } catch (ClassNotFoundException x) {
    163             throw new ConfigurationError(
    164                 "Provider " + className + " not found", x);
    165         } catch (Exception x) {
    166             throw new ConfigurationError(
    167                 "Provider " + className + " could not be instantiated: " + x,
    168                 x);
    169         }
    170     }
    171 
    172     /**
    173      * Finds the implementation Class object in the specified order.  Main
    174      * entry point.
    175      * Package private so this code can be shared.
    176      *
    177      * @param factoryId Name of the factory to find, same as a property name
    178      * @param fallbackClassName Implementation class name, if nothing else is found.  Use null to mean no fallback.
    179      *
    180      * @return Class Object of factory, never null
    181      *
    182      * @throws ConfigurationError If Class cannot be found.
    183      */
    184     static Object find(String factoryId, String fallbackClassName)
    185         throws ConfigurationError {
    186 
    187         ClassLoader classLoader = findClassLoader();
    188 
    189         // Use the system property first
    190         try {
    191             String systemProp = SecuritySupport.getSystemProperty(factoryId);
    192             if (systemProp != null && systemProp.length() > 0) {
    193                 if (debug) debugPrintln("found " + systemProp + " in the system property " + factoryId);
    194                 return newInstance(systemProp, classLoader);
    195             }
    196         } catch (SecurityException se) {
    197             ; // NOP, explicitly ignore SecurityException
    198         }
    199 
    200         // try to read from $java.home/lib/jaxp.properties
    201         try {
    202             String javah = SecuritySupport.getSystemProperty("java.home");
    203             String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties";
    204             String factoryClassName = null;
    205             if (firstTime) {
    206                 synchronized (cacheProps) {
    207                     if (firstTime) {
    208                         File f = new File(configFile);
    209                         firstTime = false;
    210                         if (SecuritySupport.doesFileExist(f)) {
    211                             if (debug) debugPrintln("Read properties file " + f);
    212                             cacheProps.load(SecuritySupport.getFileInputStream(f));
    213                         }
    214                     }
    215                 }
    216             }
    217             factoryClassName = cacheProps.getProperty(factoryId);
    218             if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties");
    219 
    220             if (factoryClassName != null) {
    221                 return newInstance(factoryClassName, classLoader);
    222             }
    223         } catch (Exception ex) {
    224             if (debug) {
    225                 ex.printStackTrace();
    226             }
    227         }
    228 
    229         // Try Jar Service Provider Mechanism
    230         Object provider = findJarServiceProvider(factoryId);
    231         if (provider != null) {
    232             return provider;
    233         }
    234 
    235         if (fallbackClassName == null) {
    236             throw new ConfigurationError(
    237                 "Provider for " + factoryId + " cannot be found", null);
    238         }
    239 
    240         if (debug) debugPrintln("loaded from fallback value: " + fallbackClassName);
    241         return newInstance(fallbackClassName, classLoader);
    242     }
    243 
    244     /*
    245      * Try to find provider using Jar Service Provider Mechanism
    246      *
    247      * @return instance of provider class if found or null
    248      */
    249     private static Object findJarServiceProvider(String factoryId)
    250         throws ConfigurationError
    251     {
    252 
    253         String serviceId = "META-INF/services/" + factoryId;
    254         InputStream is = null;
    255 
    256         // First try the Context ClassLoader
    257         ClassLoader cl = SecuritySupport.getContextClassLoader();
    258         if (cl != null) {
    259             is = SecuritySupport.getResourceAsStream(cl, serviceId);
    260 
    261             // If no provider found then try the current ClassLoader
    262             if (is == null) {
    263                 cl = FactoryFinder.class.getClassLoader();
    264                 is = SecuritySupport.getResourceAsStream(cl, serviceId);
    265             }
    266         } else {
    267             // No Context ClassLoader, try the current
    268             // ClassLoader
    269             cl = FactoryFinder.class.getClassLoader();
    270             is = SecuritySupport.getResourceAsStream(cl, serviceId);
    271         }
    272 
    273         if (is == null) {
    274             // No provider found
    275             return null;
    276         }
    277 
    278         if (debug) debugPrintln("found jar resource=" + serviceId +
    279                " using ClassLoader: " + cl);
    280 
    281         BufferedReader rd;
    282         try {
    283             rd = new BufferedReader(new InputStreamReader(is, "UTF-8"), DEFAULT_LINE_LENGTH);
    284         } catch (java.io.UnsupportedEncodingException e) {
    285             rd = new BufferedReader(new InputStreamReader(is), DEFAULT_LINE_LENGTH);
    286         }
    287 
    288         String factoryClassName = null;
    289         try {
    290             // XXX Does not handle all possible input as specified by the
    291             // Jar Service Provider specification
    292             factoryClassName = rd.readLine();
    293         }
    294         catch (IOException x) {
    295             // No provider found
    296             return null;
    297         }
    298         finally {
    299             try {
    300                 // try to close the reader.
    301                 rd.close();
    302             }
    303             // Ignore the exception.
    304             catch (IOException exc) {}
    305         }
    306 
    307         if (factoryClassName != null &&
    308             ! "".equals(factoryClassName)) {
    309             if (debug) debugPrintln("found in resource, value="
    310                    + factoryClassName);
    311 
    312             return newInstance(factoryClassName, cl);
    313         }
    314 
    315         // No provider found
    316         return null;
    317     }
    318 
    319     /**
    320      * <p>Configuration Error.</p>
    321      */
    322     static class ConfigurationError extends Error {
    323 
    324         private static final long serialVersionUID = -3644413026244211347L;
    325 
    326         /**
    327          * <p>Exception that caused the error.</p>
    328          */
    329         private Exception exception;
    330 
    331         /**
    332          * <p>Construct a new instance with the specified detail string and
    333          * exception.</p>
    334          *
    335          * @param msg Detail message for this error.
    336          * @param x Exception that caused the error.
    337          */
    338         ConfigurationError(String msg, Exception x) {
    339             super(msg);
    340             this.exception = x;
    341         }
    342 
    343         /**
    344          * <p>Get the Exception that caused the error.</p>
    345          *
    346          * @return Exception that caused the error.
    347          */
    348         Exception getException() {
    349             return exception;
    350         }
    351     }
    352 
    353 
    354 
    355     /**
    356      * Returns the location where the given Class is loaded from.
    357      *
    358      * @param clazz Class to find load location.
    359      *
    360      * @return Location where class would be loaded from.
    361      */
    362     private static String which(Class clazz) {
    363         try {
    364             String classnameAsResource = clazz.getName().replace('.', '/') + ".class";
    365 
    366             ClassLoader loader = clazz.getClassLoader();
    367 
    368             URL it;
    369 
    370             if (loader != null) {
    371                 it = loader.getResource(classnameAsResource);
    372             } else {
    373                 it = ClassLoader.getSystemResource(classnameAsResource);
    374             }
    375 
    376             if (it != null) {
    377                 return it.toString();
    378             }
    379         }
    380         // The VM ran out of memory or there was some other serious problem. Re-throw.
    381         catch (VirtualMachineError vme) {
    382             throw vme;
    383         }
    384         // ThreadDeath should always be re-thrown
    385         catch (ThreadDeath td) {
    386             throw td;
    387         }
    388         catch (Throwable t) {
    389             // work defensively.
    390             if (debug) {
    391                 t.printStackTrace();
    392             }
    393         }
    394         return "unknown location";
    395     }
    396 }
    397