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