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