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