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