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