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