1 /* 2 * Copyright (c) 2004, 2013, 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.File; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.Modifier; 31 import java.net.URL; 32 import java.security.AccessControlContext; 33 import java.security.AccessController; 34 import java.security.PrivilegedAction; 35 import java.util.Properties; 36 import java.util.ServiceConfigurationError; 37 import java.util.ServiceLoader; 38 39 /** 40 * Implementation of {@link XPathFactory#newInstance(String)}. 41 * 42 * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> 43 * @since 1.5 44 */ 45 class XPathFactoryFinder { 46 private static final String DEFAULT_PACKAGE = "com.sun.org.apache.xpath.internal"; 47 48 private static final SecuritySupport ss = new SecuritySupport() ; 49 /** debug support code. */ 50 private static boolean debug = false; 51 static { 52 // Use try/catch block to support applets 53 try { 54 debug = ss.getSystemProperty("jaxp.debug") != null; 55 } catch (Exception unused) { 56 debug = false; 57 } 58 } 59 60 /** 61 * <p>Cache properties for performance.</p> 62 */ 63 private static final Properties cacheProps = new Properties(); 64 65 /** 66 * <p>First time requires initialization overhead.</p> 67 */ 68 private volatile static boolean firstTime = true; 69 70 /** 71 * <p>Conditional debug printing.</p> 72 * 73 * @param msg to print 74 */ 75 private static void debugPrintln(String msg) { 76 if (debug) { 77 System.err.println("JAXP: " + msg); 78 } 79 } 80 81 /** 82 * <p><code>ClassLoader</code> to use to find <code>XPathFactory</code>.</p> 83 */ 84 private final ClassLoader classLoader; 85 86 /** 87 * <p>Constructor that specifies <code>ClassLoader</code> to use 88 * to find <code>XPathFactory</code>.</p> 89 * 90 * @param loader 91 * to be used to load resource and {@link XPathFactory} 92 * implementations during the resolution process. 93 * If this parameter is null, the default system class loader 94 * will be used. 95 */ 96 public XPathFactoryFinder(ClassLoader loader) { 97 this.classLoader = loader; 98 if( debug ) { 99 debugDisplayClassLoader(); 100 } 101 } 102 103 private void debugDisplayClassLoader() { 104 try { 105 if( classLoader == ss.getContextClassLoader() ) { 106 debugPrintln("using thread context class loader ("+classLoader+") for search"); 107 return; 108 } 109 } catch( Throwable unused ) { 110 // getContextClassLoader() undefined in JDK1.1 111 } 112 113 if( classLoader==ClassLoader.getSystemClassLoader() ) { 114 debugPrintln("using system class loader ("+classLoader+") for search"); 115 return; 116 } 117 118 debugPrintln("using class loader ("+classLoader+") for search"); 119 } 120 121 /** 122 * <p>Creates a new {@link XPathFactory} object for the specified 123 * object model.</p> 124 * 125 * @param uri 126 * Identifies the underlying object model. 127 * 128 * @return <code>null</code> if the callee fails to create one. 129 * 130 * @throws NullPointerException 131 * If the parameter is null. 132 */ 133 public XPathFactory newFactory(String uri) throws XPathFactoryConfigurationException { 134 if (uri == null) { 135 throw new NullPointerException(); 136 } 137 XPathFactory f = _newFactory(uri); 138 if (f != null) { 139 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + uri); 140 } else { 141 debugPrintln("unable to find a factory for " + uri); 142 } 143 return f; 144 } 145 146 /** 147 * <p>Lookup a {@link XPathFactory} for the given object model.</p> 148 * 149 * @param uri identifies the object model. 150 * 151 * @return {@link XPathFactory} for the given object model. 152 */ 153 private XPathFactory _newFactory(String uri) throws XPathFactoryConfigurationException { 154 XPathFactory xpathFactory = null; 155 156 String propertyName = SERVICE_CLASS.getName() + ":" + uri; 157 158 // system property look up 159 try { 160 debugPrintln("Looking up system property '"+propertyName+"'" ); 161 String r = ss.getSystemProperty(propertyName); 162 if(r!=null) { 163 debugPrintln("The value is '"+r+"'"); 164 xpathFactory = createInstance(r, true); 165 if (xpathFactory != null) { 166 return xpathFactory; 167 } 168 } else 169 debugPrintln("The property is undefined."); 170 } catch( Throwable t ) { 171 if( debug ) { 172 debugPrintln("failed to look up system property '"+propertyName+"'" ); 173 t.printStackTrace(); 174 } 175 } 176 177 String javah = ss.getSystemProperty( "java.home" ); 178 String configFile = javah + File.separator + 179 "conf" + File.separator + "jaxp.properties"; 180 181 // try to read from $java.home/conf/jaxp.properties 182 try { 183 if(firstTime){ 184 synchronized(cacheProps){ 185 if(firstTime){ 186 File f=new File( configFile ); 187 firstTime = false; 188 if(ss.doesFileExist(f)){ 189 debugPrintln("Read properties file " + f); 190 cacheProps.load(ss.getFileInputStream(f)); 191 } 192 } 193 } 194 } 195 final String factoryClassName = cacheProps.getProperty(propertyName); 196 debugPrintln("found " + factoryClassName + " in $java.home/conf/jaxp.properties"); 197 198 if (factoryClassName != null) { 199 xpathFactory = createInstance(factoryClassName, true); 200 if(xpathFactory != null){ 201 return xpathFactory; 202 } 203 } 204 } catch (Exception ex) { 205 if (debug) { 206 ex.printStackTrace(); 207 } 208 } 209 210 // Try with ServiceLoader 211 assert xpathFactory == null; 212 xpathFactory = findServiceProvider(uri); 213 214 // The following assertion should always be true. 215 // Uncomment it, recompile, and run with -ea in case of doubts: 216 // assert xpathFactory == null || xpathFactory.isObjectModelSupported(uri); 217 218 if (xpathFactory != null) { 219 return xpathFactory; 220 } 221 222 // platform default 223 if(uri.equals(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) { 224 debugPrintln("attempting to use the platform default W3C DOM XPath lib"); 225 return createInstance("com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl", true); 226 } 227 228 debugPrintln("all things were tried, but none was found. bailing out."); 229 return null; 230 } 231 232 /** <p>Create class using appropriate ClassLoader.</p> 233 * 234 * @param className Name of class to create. 235 * @return Created class or <code>null</code>. 236 */ 237 private Class<?> createClass(String className) { 238 Class clazz; 239 // make sure we have access to restricted packages 240 boolean internal = false; 241 if (System.getSecurityManager() != null) { 242 if (className != null && className.startsWith(DEFAULT_PACKAGE)) { 243 internal = true; 244 } 245 } 246 247 // use approprite ClassLoader 248 try { 249 if (classLoader != null && !internal) { 250 clazz = Class.forName(className, false, classLoader); 251 } else { 252 clazz = Class.forName(className); 253 } 254 } catch (Throwable t) { 255 if(debug) { 256 t.printStackTrace(); 257 } 258 return null; 259 } 260 261 return clazz; 262 } 263 264 /** 265 * <p>Creates an instance of the specified and returns it.</p> 266 * 267 * @param className 268 * fully qualified class name to be instantiated. 269 * 270 * @return null 271 * if it fails. Error messages will be printed by this method. 272 */ 273 XPathFactory createInstance( String className ) 274 throws XPathFactoryConfigurationException 275 { 276 return createInstance( className, false ); 277 } 278 279 XPathFactory createInstance( String className, boolean useServicesMechanism ) 280 throws XPathFactoryConfigurationException 281 { 282 XPathFactory xPathFactory = null; 283 284 debugPrintln("createInstance(" + className + ")"); 285 286 // get Class from className 287 Class<?> clazz = createClass(className); 288 if (clazz == null) { 289 debugPrintln("failed to getClass(" + className + ")"); 290 return null; 291 } 292 debugPrintln("loaded " + className + " from " + which(clazz)); 293 294 // instantiate Class as a XPathFactory 295 try { 296 if (!useServicesMechanism) { 297 xPathFactory = newInstanceNoServiceLoader(clazz); 298 } 299 if (xPathFactory == null) { 300 xPathFactory = (XPathFactory) clazz.newInstance(); 301 } 302 } catch (ClassCastException classCastException) { 303 debugPrintln("could not instantiate " + clazz.getName()); 304 if (debug) { 305 classCastException.printStackTrace(); 306 } 307 return null; 308 } catch (IllegalAccessException illegalAccessException) { 309 debugPrintln("could not instantiate " + clazz.getName()); 310 if (debug) { 311 illegalAccessException.printStackTrace(); 312 } 313 return null; 314 } catch (InstantiationException instantiationException) { 315 debugPrintln("could not instantiate " + clazz.getName()); 316 if (debug) { 317 instantiationException.printStackTrace(); 318 } 319 return null; 320 } 321 322 return xPathFactory; 323 } 324 /** 325 * Try to construct using newXPathFactoryNoServiceLoader 326 * method if available. 327 */ 328 private static XPathFactory newInstanceNoServiceLoader( 329 Class<?> providerClass 330 ) throws XPathFactoryConfigurationException { 331 // Retain maximum compatibility if no security manager. 332 if (System.getSecurityManager() == null) { 333 return null; 334 } 335 try { 336 Method creationMethod = 337 providerClass.getDeclaredMethod( 338 "newXPathFactoryNoServiceLoader" 339 ); 340 final int modifiers = creationMethod.getModifiers(); 341 342 // Do not call "newXPathFactoryNoServiceLoader" if it's 343 // not public static. 344 if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { 345 return null; 346 } 347 348 // Only calls "newXPathFactoryNoServiceLoader" if it's 349 // declared to return an instance of XPathFactory. 350 final Class<?> returnType = creationMethod.getReturnType(); 351 if (SERVICE_CLASS.isAssignableFrom(returnType)) { 352 return SERVICE_CLASS.cast(creationMethod.invoke(null, (Object[])null)); 353 } else { 354 // Should not happen since 355 // XPathFactoryImpl.newXPathFactoryNoServiceLoader is 356 // declared to return XPathFactory. 357 throw new ClassCastException(returnType 358 + " cannot be cast to " + SERVICE_CLASS); 359 } 360 } catch (ClassCastException e) { 361 throw new XPathFactoryConfigurationException(e); 362 } catch (NoSuchMethodException exc) { 363 return null; 364 } catch (Exception exc) { 365 return null; 366 } 367 } 368 369 // Call isObjectModelSupportedBy with initial context. 370 private boolean isObjectModelSupportedBy(final XPathFactory factory, 371 final String objectModel, 372 AccessControlContext acc) { 373 return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { 374 public Boolean run() { 375 return factory.isObjectModelSupported(objectModel); 376 } 377 }, acc); 378 } 379 380 /** 381 * Finds a service provider subclass of XPathFactory that supports the 382 * given object model using the ServiceLoader. 383 * 384 * @param objectModel URI of object model to support. 385 * @return An XPathFactory supporting the specified object model, or null 386 * if none is found. 387 * @throws XPathFactoryConfigurationException if a configuration error is found. 388 */ 389 private XPathFactory findServiceProvider(final String objectModel) 390 throws XPathFactoryConfigurationException { 391 392 assert objectModel != null; 393 // store current context. 394 final AccessControlContext acc = AccessController.getContext(); 395 try { 396 return AccessController.doPrivileged(new PrivilegedAction<XPathFactory>() { 397 public XPathFactory run() { 398 final ServiceLoader<XPathFactory> loader = 399 ServiceLoader.load(SERVICE_CLASS); 400 for (XPathFactory factory : loader) { 401 // restore initial context to call 402 // factory.isObjectModelSupportedBy 403 if (isObjectModelSupportedBy(factory, objectModel, acc)) { 404 return factory; 405 } 406 } 407 return null; // no factory found. 408 } 409 }); 410 } catch (ServiceConfigurationError error) { 411 throw new XPathFactoryConfigurationException(error); 412 } 413 } 414 415 private static final Class<XPathFactory> SERVICE_CLASS = XPathFactory.class; 416 417 private static String which( Class clazz ) { 418 return which( clazz.getName(), clazz.getClassLoader() ); 419 } 420 421 /** 422 * <p>Search the specified classloader for the given classname.</p> 423 * 424 * @param classname the fully qualified name of the class to search for 425 * @param loader the classloader to search 426 * 427 * @return the source location of the resource, or null if it wasn't found 428 */ 429 private static String which(String classname, ClassLoader loader) { 430 431 String classnameAsResource = classname.replace('.', '/') + ".class"; 432 433 if( loader==null ) loader = ClassLoader.getSystemClassLoader(); 434 435 //URL it = loader.getResource(classnameAsResource); 436 URL it = ss.getResourceAsURL(loader, classnameAsResource); 437 if (it != null) { 438 return it.toString(); 439 } else { 440 return null; 441 } 442 } 443 }