1 /* 2 * Copyright (c) 2003, 2016, 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.File; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.Modifier; 31 import java.security.AccessControlContext; 32 import java.security.AccessController; 33 import java.security.PrivilegedAction; 34 import java.util.Properties; 35 import java.util.ServiceConfigurationError; 36 import java.util.ServiceLoader; 37 import java.util.function.Supplier; 38 39 /** 40 * Implementation of {@link SchemaFactory#newInstance(String)}. 41 * 42 * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> 43 * @since 1.5 44 */ 45 class SchemaFactoryFinder { 46 47 /** debug support code. */ 48 private static boolean debug = false; 49 /** 50 *<p> Take care of restrictions imposed by java security model </p> 51 */ 52 private static final SecuritySupport ss = new SecuritySupport(); 53 private static final String DEFAULT_PACKAGE = "com.sun.org.apache.xerces.internal"; 54 /** 55 * <p>Cache properties for performance.</p> 56 */ 57 private static final Properties cacheProps = new Properties(); 58 59 /** 60 * <p>First time requires initialization overhead.</p> 61 */ 62 private static volatile boolean firstTime = true; 63 64 static { 65 // Use try/catch block to support applets 66 try { 67 debug = ss.getSystemProperty("jaxp.debug") != null; 68 } catch (Exception unused) { 69 debug = false; 70 } 71 } 72 73 /** 74 * <p>Conditional debug printing.</p> 75 * 76 * @param msgGen Supplier function that returns debug message 77 */ 78 private static void debugPrintln(Supplier<String> msgGen) { 79 if (debug) { 80 System.err.println("JAXP: " + msgGen.get()); 81 } 82 } 83 84 /** 85 * <p><code>ClassLoader</code> to use to find <code>SchemaFactory</code>.</p> 86 */ 87 private final ClassLoader classLoader; 88 89 /** 90 * <p>Constructor that specifies <code>ClassLoader</code> to use 91 * to find <code>SchemaFactory</code>.</p> 92 * 93 * @param loader 94 * to be used to load resource, {@link SchemaFactory}, and 95 * {@link SchemaFactoryLoader} implementations during 96 * the resolution process. 97 * If this parameter is null, the default system class loader 98 * will be used. 99 */ 100 public SchemaFactoryFinder(ClassLoader loader) { 101 this.classLoader = loader; 102 if( debug ) { 103 debugDisplayClassLoader(); 104 } 105 } 106 107 private void debugDisplayClassLoader() { 108 try { 109 if( classLoader == ss.getContextClassLoader() ) { 110 debugPrintln(()->"using thread context class loader ("+classLoader+") for search"); 111 return; 112 } 113 } catch( Throwable unused ) { 114 // getContextClassLoader() undefined in JDK1.1 115 } 116 117 if( classLoader==ClassLoader.getSystemClassLoader() ) { 118 debugPrintln(()->"using system class loader ("+classLoader+") for search"); 119 return; 120 } 121 122 debugPrintln(()->"using class loader ("+classLoader+") for search"); 123 } 124 125 /** 126 * <p>Creates a new {@link SchemaFactory} object for the specified 127 * schema language.</p> 128 * 129 * @param schemaLanguage 130 * See {@link SchemaFactory Schema Language} table in <code>SchemaFactory</code> 131 * for the list of available schema languages. 132 * 133 * @return <code>null</code> if the callee fails to create one. 134 * 135 * @throws NullPointerException 136 * If the <code>schemaLanguage</code> parameter is null. 137 * @throws SchemaFactoryConfigurationError 138 * If a configuration error is encountered. 139 */ 140 public SchemaFactory newFactory(String schemaLanguage) { 141 if(schemaLanguage==null) { 142 throw new NullPointerException(); 143 } 144 SchemaFactory f = _newFactory(schemaLanguage); 145 if (f != null) { 146 debugPrintln(()->"factory '" + f.getClass().getName() + "' was found for " + schemaLanguage); 147 } else { 148 debugPrintln(()->"unable to find a factory for " + schemaLanguage); 149 } 150 return f; 151 } 152 153 /** 154 * <p>Lookup a <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.</p> 155 * 156 * @param schemaLanguage Schema language to lookup <code>SchemaFactory</code> for. 157 * 158 * @return <code>SchemaFactory</code> for the given <code>schemaLanguage</code>. 159 */ 160 private SchemaFactory _newFactory(String schemaLanguage) { 161 SchemaFactory sf; 162 163 String propertyName = SERVICE_CLASS.getName() + ":" + schemaLanguage; 164 165 // system property look up 166 try { 167 debugPrintln(()->"Looking up system property '"+propertyName+"'" ); 168 String r = ss.getSystemProperty(propertyName); 169 if(r!=null) { 170 debugPrintln(()->"The value is '"+r+"'"); 171 sf = createInstance(r, true); 172 if(sf!=null) return sf; 173 } else 174 debugPrintln(()->"The property is undefined."); 175 } catch( Throwable t ) { 176 if( debug ) { 177 debugPrintln(()->"failed to look up system property '"+propertyName+"'" ); 178 t.printStackTrace(); 179 } 180 } 181 182 String javah = ss.getSystemProperty( "java.home" ); 183 String configFile = javah + File.separator + 184 "conf" + File.separator + "jaxp.properties"; 185 186 187 // try to read from $java.home/conf/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 final String factoryClassName = cacheProps.getProperty(propertyName); 202 debugPrintln(()->"found " + factoryClassName + " in $java.home/conf/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 // Try with ServiceLoader 217 final SchemaFactory factoryImpl = findServiceProvider(schemaLanguage); 218 219 // The following assertion should always be true. 220 // Uncomment it, recompile, and run with -ea in case of doubts: 221 // assert factoryImpl == null || factoryImpl.isSchemaLanguageSupported(schemaLanguage); 222 223 if (factoryImpl != null) { 224 return factoryImpl; 225 } 226 227 // platform default 228 if(schemaLanguage.equals("http://www.w3.org/2001/XMLSchema")) { 229 debugPrintln(()->"attempting to use the platform default XML Schema validator"); 230 return createInstance("com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory", true); 231 } 232 233 debugPrintln(()->"all things were tried, but none was found. bailing out."); 234 return null; 235 } 236 237 /** <p>Create class using appropriate ClassLoader.</p> 238 * 239 * @param className Name of class to create. 240 * @return Created class or <code>null</code>. 241 */ 242 private Class<?> createClass(String className) { 243 Class<?> clazz; 244 // make sure we have access to restricted packages 245 boolean internal = false; 246 if (System.getSecurityManager() != null) { 247 if (className != null && className.startsWith(DEFAULT_PACKAGE)) { 248 internal = true; 249 } 250 } 251 252 try { 253 if (classLoader != null && !internal) { 254 clazz = Class.forName(className, false, classLoader); 255 } else { 256 clazz = Class.forName(className); 257 } 258 } catch (Throwable t) { 259 if(debug) { 260 t.printStackTrace(); 261 } 262 return null; 263 } 264 265 return clazz; 266 } 267 268 /** 269 * <p>Creates an instance of the specified and returns it.</p> 270 * 271 * @param className 272 * fully qualified class name to be instantiated. 273 * 274 * @return null 275 * if it fails. Error messages will be printed by this method. 276 */ 277 SchemaFactory createInstance( String className ) { 278 return createInstance( className, false ); 279 } 280 281 SchemaFactory createInstance( String className, boolean useServicesMechanism ) { 282 SchemaFactory schemaFactory = 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 SchemaFactory 295 try { 296 if (!SchemaFactory.class.isAssignableFrom(clazz)) { 297 throw new ClassCastException(clazz.getName() 298 + " cannot be cast to " + SchemaFactory.class); 299 } 300 if (!useServicesMechanism) { 301 schemaFactory = newInstanceNoServiceLoader(clazz); 302 } 303 if (schemaFactory == null) { 304 schemaFactory = (SchemaFactory) 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 schemaFactory; 327 } 328 329 /** 330 * Try to construct using newXMLSchemaFactoryNoServiceLoader 331 * method if available. 332 */ 333 private static SchemaFactory newInstanceNoServiceLoader( 334 Class<?> providerClass 335 ) { 336 // Retain maximum compatibility if no security manager. 337 if (System.getSecurityManager() == null) { 338 return null; 339 } 340 try { 341 final Method creationMethod = 342 providerClass.getDeclaredMethod( 343 "newXMLSchemaFactoryNoServiceLoader" 344 ); 345 final int modifiers = creationMethod.getModifiers(); 346 347 // Do not call the method if it's not public static. 348 if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { 349 return null; 350 } 351 352 // Only calls "newXMLSchemaFactoryNoServiceLoader" if it's 353 // declared to return an instance of SchemaFactory. 354 final Class<?> returnType = creationMethod.getReturnType(); 355 if (SERVICE_CLASS.isAssignableFrom(returnType)) { 356 return SERVICE_CLASS.cast(creationMethod.invoke(null, (Object[])null)); 357 } else { 358 // Should not happen since 359 // XMLSchemaFactory.newXMLSchemaFactoryNoServiceLoader is 360 // declared to return XMLSchemaFactory. 361 throw new ClassCastException(returnType 362 + " cannot be cast to " + SERVICE_CLASS); 363 } 364 } catch(ClassCastException e) { 365 throw new SchemaFactoryConfigurationError(e.getMessage(), e); 366 } catch (NoSuchMethodException exc) { 367 return null; 368 } catch (Exception exc) { 369 return null; 370 } 371 } 372 373 // Call isSchemaLanguageSupported with initial context. 374 private boolean isSchemaLanguageSupportedBy(final SchemaFactory factory, 375 final String schemaLanguage, 376 AccessControlContext acc) { 377 return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { 378 public Boolean run() { 379 return factory.isSchemaLanguageSupported(schemaLanguage); 380 } 381 }, acc); 382 } 383 384 /** 385 * Finds a service provider subclass of SchemaFactory that supports the 386 * given schema language using the ServiceLoader. 387 * 388 * @param schemaLanguage The schema language for which we seek a factory. 389 * @return A SchemaFactory supporting the specified schema language, or null 390 * if none is found. 391 * @throws SchemaFactoryConfigurationError if a configuration error is found. 392 */ 393 private SchemaFactory findServiceProvider(final String schemaLanguage) { 394 assert schemaLanguage != null; 395 // store current context. 396 final AccessControlContext acc = AccessController.getContext(); 397 try { 398 return AccessController.doPrivileged(new PrivilegedAction<SchemaFactory>() { 399 public SchemaFactory run() { 400 final ServiceLoader<SchemaFactory> loader = 401 ServiceLoader.load(SERVICE_CLASS); 402 for (SchemaFactory factory : loader) { 403 // restore initial context to call 404 // factory.isSchemaLanguageSupported 405 if (isSchemaLanguageSupportedBy(factory, schemaLanguage, acc)) { 406 return factory; 407 } 408 } 409 return null; // no factory found. 410 } 411 }); 412 } catch (ServiceConfigurationError error) { 413 throw new SchemaFactoryConfigurationError( 414 "Provider for " + SERVICE_CLASS + " cannot be created", error); 415 } 416 } 417 418 private static final Class<SchemaFactory> SERVICE_CLASS = SchemaFactory.class; 419 420 421 // Used for debugging purposes 422 private static String which( Class<?> clazz ) { 423 return ss.getClassSource(clazz); 424 } 425 }