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