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