1 /* 2 * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.xml.validation; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.InputStreamReader; 33 import java.lang.reflect.Method; 34 import java.lang.reflect.InvocationTargetException; 35 import java.net.URL; 36 import java.util.ArrayList; 37 import java.util.Enumeration; 38 import java.util.Iterator; 39 import java.util.NoSuchElementException; 40 import java.util.Properties; 41 42 /** 43 * Implementation of {@link SchemaFactory#newInstance(String)}. 44 * 45 * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> 46 * @version $Revision: 1.8 $, $Date: 2010-11-01 04:36:13 $ 47 * @since 1.5 48 */ 49 class SchemaFactoryFinder { 50 51 /** debug support code. */ 52 private static boolean debug = false; 53 /** 54 *<p> Take care of restrictions imposed by java security model </p> 55 */ 56 private static SecuritySupport ss = new SecuritySupport(); 57 /** 58 * <p>Cache properties for performance.</p> 59 */ 60 private static Properties cacheProps = new Properties(); 61 62 /** 63 * <p>First time requires initialization overhead.</p> 64 */ 65 private static volatile boolean firstTime = true; 66 67 static { 68 // Use try/catch block to support applets 69 try { 70 debug = ss.getSystemProperty("jaxp.debug") != null; 71 } catch (Exception _) { 72 debug = false; 73 } 74 } 75 76 /** 77 * <p>Conditional debug printing.</p> 78 * 79 * @param msg to print 80 */ 81 private static void debugPrintln(String msg) { 82 if (debug) { 83 System.err.println("JAXP: " + msg); 84 } 85 } 86 87 /** 88 * <p><code>ClassLoader</code> to use to find <code>SchemaFactory</code>.</p> 89 */ 90 private final ClassLoader classLoader; 91 92 /** 93 * <p>Constructor that specifies <code>ClassLoader</code> to use 94 * to find <code>SchemaFactory</code>.</p> 95 * 96 * @param loader 97 * to be used to load resource, {@link SchemaFactory}, and 98 * {@link SchemaFactoryLoader} implementations during 99 * the resolution process. 100 * If this parameter is null, the default system class loader 101 * will be used. 102 */ 103 public SchemaFactoryFinder(ClassLoader loader) { 104 this.classLoader = loader; 105 if( debug ) { 106 debugDisplayClassLoader(); 107 } 108 } 109 110 private void debugDisplayClassLoader() { 111 try { 112 if( classLoader == ss.getContextClassLoader() ) { 113 debugPrintln("using thread context class loader ("+classLoader+") for search"); 114 return; 115 } 116 } catch( Throwable _ ) { 117 ; // getContextClassLoader() undefined in JDK1.1 118 } 119 120 if( classLoader==ClassLoader.getSystemClassLoader() ) { 121 debugPrintln("using system class loader ("+classLoader+") for search"); 122 return; 123 } 124 125 debugPrintln("using class loader ("+classLoader+") for search"); 126 } 127 128 /** 129 * <p>Creates a new {@link SchemaFactory} object for the specified 130 * schema language.</p> 131 * 132 * @param schemaLanguage 133 * See {@link SchemaFactory Schema Language} table in <code>SchemaFactory</code> 134 * for the list of available schema languages. 135 * 136 * @return <code>null</code> if the callee fails to create one. 137 * 138 * @throws NullPointerException 139 * If the <code>schemaLanguage</code> parameter is null. 140 */ 141 public SchemaFactory newFactory(String schemaLanguage) { 142 if(schemaLanguage==null) throw new NullPointerException(); 143 SchemaFactory f = _newFactory(schemaLanguage); 144 if (f != null) { 145 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + schemaLanguage); 146 } else { 147 debugPrintln("unable to find a factory for " + schemaLanguage); 148 } 149 return f; 150 } 151 152 /** 153 * <p>Lookup a <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.</p> 154 * 155 * @param schemaLanguage Schema language to lookup <code>SchemaFactory</code> for. 156 * 157 * @return <code>SchemaFactory</code> for the given <code>schemaLanguage</code>. 158 */ 159 private SchemaFactory _newFactory(String schemaLanguage) { 160 SchemaFactory sf; 161 162 String propertyName = SERVICE_CLASS.getName() + ":" + schemaLanguage; 163 164 // system property look up 165 try { 166 debugPrintln("Looking up system property '"+propertyName+"'" ); 167 String r = ss.getSystemProperty(propertyName); 168 if(r!=null) { 169 debugPrintln("The value is '"+r+"'"); 170 sf = createInstance(r, true); 171 if(sf!=null) return sf; 172 } else 173 debugPrintln("The property is undefined."); 174 } catch( Throwable t ) { 175 if( debug ) { 176 debugPrintln("failed to look up system property '"+propertyName+"'" ); 177 t.printStackTrace(); 178 } 179 } 180 181 String javah = ss.getSystemProperty( "java.home" ); 182 String configFile = javah + File.separator + 183 "lib" + File.separator + "jaxp.properties"; 184 185 String factoryClassName = null ; 186 187 // try to read from $java.home/lib/jaxp.properties 188 try { 189 if(firstTime){ 190 synchronized(cacheProps){ 191 if(firstTime){ 192 File f=new File( configFile ); 193 firstTime = false; 194 if(ss.doesFileExist(f)){ 195 debugPrintln("Read properties file " + f); 196 cacheProps.load(ss.getFileInputStream(f)); 197 } 198 } 199 } 200 } 201 factoryClassName = cacheProps.getProperty(propertyName); 202 debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties"); 203 204 if (factoryClassName != null) { 205 sf = createInstance(factoryClassName, true); 206 if(sf != null){ 207 return sf; 208 } 209 } 210 } catch (Exception ex) { 211 if (debug) { 212 ex.printStackTrace(); 213 } 214 } 215 216 /** 217 // try to read from $java.home/lib/jaxp.properties 218 try { 219 String javah = ss.getSystemProperty( "java.home" ); 220 String configFile = javah + File.separator + 221 "lib" + File.separator + "jaxp.properties"; 222 File f = new File( configFile ); 223 if( ss.doesFileExist(f)) { 224 sf = loadFromProperty( 225 propertyName,f.getAbsolutePath(), new FileInputStream(f)); 226 if(sf!=null) return sf; 227 } else { 228 debugPrintln("Tried to read "+ f.getAbsolutePath()+", but it doesn't exist."); 229 } 230 } catch(Throwable e) { 231 if( debug ) { 232 debugPrintln("failed to read $java.home/lib/jaxp.properties"); 233 e.printStackTrace(); 234 } 235 } 236 */ 237 238 // try META-INF/services files 239 Iterator sitr = createServiceFileIterator(); 240 while(sitr.hasNext()) { 241 URL resource = (URL)sitr.next(); 242 debugPrintln("looking into " + resource); 243 try { 244 sf = loadFromService(schemaLanguage,resource.toExternalForm(), 245 ss.getURLInputStream(resource)); 246 if(sf!=null) return sf; 247 } catch(IOException e) { 248 if( debug ) { 249 debugPrintln("failed to read "+resource); 250 e.printStackTrace(); 251 } 252 } 253 } 254 255 // platform default 256 if(schemaLanguage.equals("http://www.w3.org/2001/XMLSchema")) { 257 debugPrintln("attempting to use the platform default XML Schema validator"); 258 return createInstance("com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory", true); 259 } 260 261 debugPrintln("all things were tried, but none was found. bailing out."); 262 return null; 263 } 264 265 /** <p>Create class using appropriate ClassLoader.</p> 266 * 267 * @param className Name of class to create. 268 * @return Created class or <code>null</code>. 269 */ 270 private Class createClass(String className) { 271 Class clazz; 272 273 // use approprite ClassLoader 274 try { 275 if (classLoader != null) { 276 clazz = classLoader.loadClass(className); 277 } else { 278 clazz = Class.forName(className); 279 } 280 } catch (Throwable t) { 281 if(debug) t.printStackTrace(); 282 return null; 283 } 284 285 return clazz; 286 } 287 288 /** 289 * <p>Creates an instance of the specified and returns it.</p> 290 * 291 * @param className 292 * fully qualified class name to be instanciated. 293 * 294 * @return null 295 * if it fails. Error messages will be printed by this method. 296 */ 297 SchemaFactory createInstance( String className ) { 298 return createInstance( className, false ); 299 } 300 301 SchemaFactory createInstance( String className, boolean useServicesMechanism ) { 302 SchemaFactory schemaFactory = null; 303 304 debugPrintln("createInstance(" + className + ")"); 305 306 // get Class from className 307 Class clazz = createClass(className); 308 if (clazz == null) { 309 debugPrintln("failed to getClass(" + className + ")"); 310 return null; 311 } 312 debugPrintln("loaded " + className + " from " + which(clazz)); 313 314 // instantiate Class as a SchemaFactory 315 try { 316 if (!useServicesMechanism) { 317 schemaFactory = (SchemaFactory) newInstanceNoServiceLoader(clazz); 318 } 319 if (schemaFactory == null) { 320 schemaFactory = (SchemaFactory) clazz.newInstance(); 321 } 322 } catch (ClassCastException classCastException) { 323 debugPrintln("could not instantiate " + clazz.getName()); 324 if (debug) { 325 classCastException.printStackTrace(); 326 } 327 return null; 328 } catch (IllegalAccessException illegalAccessException) { 329 debugPrintln("could not instantiate " + clazz.getName()); 330 if (debug) { 331 illegalAccessException.printStackTrace(); 332 } 333 return null; 334 } catch (InstantiationException instantiationException) { 335 debugPrintln("could not instantiate " + clazz.getName()); 336 if (debug) { 337 instantiationException.printStackTrace(); 338 } 339 return null; 340 } 341 342 return schemaFactory; 343 } 344 /** 345 * Try to construct using newTransformerFactoryNoServiceLoader 346 * method if available. 347 */ 348 private static Object newInstanceNoServiceLoader( 349 Class<?> providerClass 350 ) { 351 // Retain maximum compatibility if no security manager. 352 if (System.getSecurityManager() == null) { 353 return null; 354 } 355 try { 356 Method creationMethod = 357 providerClass.getDeclaredMethod( 358 "newXMLSchemaFactoryNoServiceLoader" 359 ); 360 return creationMethod.invoke(null, (Object[])null); 361 } catch (NoSuchMethodException exc) { 362 return null; 363 } catch (Exception exc) { 364 return null; 365 } 366 } 367 368 /** Iterator that lazily computes one value and returns it. */ 369 private static abstract class SingleIterator implements Iterator { 370 private boolean seen = false; 371 372 public final void remove() { throw new UnsupportedOperationException(); } 373 public final boolean hasNext() { return !seen; } 374 public final Object next() { 375 if(seen) throw new NoSuchElementException(); 376 seen = true; 377 return value(); 378 } 379 380 protected abstract Object value(); 381 } 382 383 /** 384 * Looks up a value in a property file 385 * while producing all sorts of debug messages. 386 * 387 * @return null 388 * if there was an error. 389 */ 390 private SchemaFactory loadFromProperty( String keyName, String resourceName, InputStream in ) 391 throws IOException { 392 debugPrintln("Reading "+resourceName ); 393 394 Properties props=new Properties(); 395 props.load(in); 396 in.close(); 397 String factoryClassName = props.getProperty(keyName); 398 if(factoryClassName != null){ 399 debugPrintln("found "+keyName+" = " + factoryClassName); 400 return createInstance(factoryClassName); 401 } else { 402 debugPrintln(keyName+" is not in the property file"); 403 return null; 404 } 405 } 406 407 /** 408 * <p>Look up a value in a property file.</p> 409 * 410 * <p>Set <code>debug</code> to <code>true</code> to trace property evaluation.</p> 411 * 412 * @param schemaLanguage Schema Language to support. 413 * @param inputName Name of <code>InputStream</code>. 414 * @param in <code>InputStream</code> of properties. 415 * 416 * @return <code>SchemaFactory</code> as determined by <code>keyName</code> value or <code>null</code> if there was an error. 417 * 418 * @throws IOException If IO error reading from <code>in</code>. 419 */ 420 private SchemaFactory loadFromService( 421 String schemaLanguage, 422 String inputName, 423 InputStream in) 424 throws IOException { 425 426 SchemaFactory schemaFactory = null; 427 final Class[] stringClassArray = {"".getClass()}; 428 final Object[] schemaLanguageObjectArray = {schemaLanguage}; 429 final String isSchemaLanguageSupportedMethod = "isSchemaLanguageSupported"; 430 431 debugPrintln("Reading " + inputName); 432 433 // read from InputStream until a match is found 434 BufferedReader configFile = new BufferedReader(new InputStreamReader(in)); 435 String line = null; 436 while ((line = configFile.readLine()) != null) { 437 // '#' is comment char 438 int comment = line.indexOf("#"); 439 switch (comment) { 440 case -1: break; // no comment 441 case 0: line = ""; break; // entire line is a comment 442 default: line = line.substring(0, comment); break; // trim comment 443 } 444 445 // trim whitespace 446 line = line.trim(); 447 448 // any content left on line? 449 if (line.length() == 0) { 450 continue; 451 } 452 453 // line content is now the name of the class 454 Class clazz = createClass(line); 455 if (clazz == null) { 456 continue; 457 } 458 459 // create an instance of the Class 460 try { 461 schemaFactory = (SchemaFactory) clazz.newInstance(); 462 } catch (ClassCastException classCastExcpetion) { 463 schemaFactory = null; 464 continue; 465 } catch (InstantiationException instantiationException) { 466 schemaFactory = null; 467 continue; 468 } catch (IllegalAccessException illegalAccessException) { 469 schemaFactory = null; 470 continue; 471 } 472 473 // does this Class support desired Schema? 474 try { 475 Method isSchemaLanguageSupported = clazz.getMethod(isSchemaLanguageSupportedMethod, stringClassArray); 476 Boolean supported = (Boolean) isSchemaLanguageSupported.invoke(schemaFactory, schemaLanguageObjectArray); 477 if (supported.booleanValue()) { 478 break; 479 } 480 } catch (NoSuchMethodException noSuchMethodException) { 481 482 } catch (IllegalAccessException illegalAccessException) { 483 484 } catch (InvocationTargetException invocationTargetException) { 485 486 } 487 schemaFactory = null; 488 } 489 490 // clean up 491 configFile.close(); 492 493 // return new instance of SchemaFactory or null 494 return schemaFactory; 495 } 496 497 /** 498 * Returns an {@link Iterator} that enumerates all 499 * the META-INF/services files that we care. 500 */ 501 private Iterator createServiceFileIterator() { 502 if (classLoader == null) { 503 return new SingleIterator() { 504 protected Object value() { 505 ClassLoader classLoader = SchemaFactoryFinder.class.getClassLoader(); 506 //return (ClassLoader.getSystemResource( SERVICE_ID )); 507 return ss.getResourceAsURL(classLoader, SERVICE_ID); 508 } 509 }; 510 } else { 511 try { 512 //final Enumeration e = classLoader.getResources(SERVICE_ID); 513 final Enumeration e = ss.getResources(classLoader, SERVICE_ID); 514 if(!e.hasMoreElements()) { 515 debugPrintln("no "+SERVICE_ID+" file was found"); 516 } 517 518 // wrap it into an Iterator. 519 return new Iterator() { 520 public void remove() { 521 throw new UnsupportedOperationException(); 522 } 523 524 public boolean hasNext() { 525 return e.hasMoreElements(); 526 } 527 528 public Object next() { 529 return e.nextElement(); 530 } 531 }; 532 } catch (IOException e) { 533 debugPrintln("failed to enumerate resources "+SERVICE_ID); 534 if(debug) e.printStackTrace(); 535 return new ArrayList().iterator(); // empty iterator 536 } 537 } 538 } 539 540 private static final Class SERVICE_CLASS = SchemaFactory.class; 541 private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName(); 542 543 544 545 private static String which( Class clazz ) { 546 return which( clazz.getName(), clazz.getClassLoader() ); 547 } 548 549 /** 550 * <p>Search the specified classloader for the given classname.</p> 551 * 552 * @param classname the fully qualified name of the class to search for 553 * @param loader the classloader to search 554 * 555 * @return the source location of the resource, or null if it wasn't found 556 */ 557 private static String which(String classname, ClassLoader loader) { 558 559 String classnameAsResource = classname.replace('.', '/') + ".class"; 560 561 if( loader==null ) loader = ClassLoader.getSystemClassLoader(); 562 563 //URL it = loader.getResource(classnameAsResource); 564 URL it = ss.getResourceAsURL(loader, classnameAsResource); 565 if (it != null) { 566 return it.toString(); 567 } else { 568 return null; 569 } 570 } 571 }