1 /* 2 * Copyright (c) 2003, 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.validation; 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 SchemaFactory#newInstance(String)}. 41 * 42 * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> 43 * @version $Revision: 1.8 $, $Date: 2010-11-01 04:36:13 $ 44 * @since 1.5 45 */ 46 class SchemaFactoryFinder { 47 48 /** debug support code. */ 49 private static boolean debug = false; 50 /** 51 *<p> Take care of restrictions imposed by java security model </p> 52 */ 53 private static final SecuritySupport ss = new SecuritySupport(); 54 /** 55 * <p>Cache properties for performance.</p> 56 */ 57 private static final Properties cacheProps = new Properties(); 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 _) { 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 _ ) { 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 "lib" + File.separator + "jaxp.properties"; 184 185 String factoryClassName; 186 187 // try to read from $java.home/lib/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 factoryClassName = cacheProps.getProperty(propertyName); 202 debugPrintln("found " + factoryClassName + " in $java.home/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 assert factoryImpl == null || factoryImpl.isSchemaLanguageSupported(schemaLanguage); 219 if (factoryImpl != null) { 220 return factoryImpl; 221 } 222 223 // platform default 224 if(schemaLanguage.equals("http://www.w3.org/2001/XMLSchema")) { 225 debugPrintln("attempting to use the platform default XML Schema validator"); 226 return createInstance("com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory", 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 241 // use approprite ClassLoader 242 try { 243 if (classLoader != null) { 244 clazz = Class.forName(className, false, classLoader); 245 } else { 246 clazz = Class.forName(className); 247 } 248 } catch (Throwable t) { 249 if (debug) { 250 t.printStackTrace(); 251 } 252 return null; 253 } 254 255 return clazz; 256 } 257 258 /** 259 * <p>Creates an instance of the specified and returns it.</p> 260 * 261 * @param className 262 * fully qualified class name to be instantiated. 263 * 264 * @return null 265 * if it fails. Error messages will be printed by this method. 266 */ 267 SchemaFactory createInstance( String className ) { 268 return createInstance( className, false ); 269 } 270 271 SchemaFactory createInstance( String className, boolean useServicesMechanism ) { 272 SchemaFactory schemaFactory = null; 273 274 debugPrintln("createInstance(" + className + ")"); 275 276 // get Class from className 277 Class<?> clazz = createClass(className); 278 if (clazz == null) { 279 debugPrintln("failed to getClass(" + className + ")"); 280 return null; 281 } 282 debugPrintln("loaded " + className + " from " + which(clazz)); 283 284 // instantiate Class as a SchemaFactory 285 try { 286 if (!SchemaFactory.class.isAssignableFrom(clazz)) { 287 throw new ClassCastException(clazz.getName() 288 + " cannot be cast to " + SchemaFactory.class); 289 } 290 if (!useServicesMechanism) { 291 schemaFactory = newInstanceNoServiceLoader(clazz); 292 } 293 if (schemaFactory == null) { 294 schemaFactory = (SchemaFactory) clazz.newInstance(); 295 } 296 } catch (ClassCastException classCastException) { 297 debugPrintln("could not instantiate " + clazz.getName()); 298 if (debug) { 299 classCastException.printStackTrace(); 300 } 301 return null; 302 } catch (IllegalAccessException illegalAccessException) { 303 debugPrintln("could not instantiate " + clazz.getName()); 304 if (debug) { 305 illegalAccessException.printStackTrace(); 306 } 307 return null; 308 } catch (InstantiationException instantiationException) { 309 debugPrintln("could not instantiate " + clazz.getName()); 310 if (debug) { 311 instantiationException.printStackTrace(); 312 } 313 return null; 314 } 315 316 return schemaFactory; 317 } 318 319 /** 320 * Try to construct using newXMLSchemaFactoryNoServiceLoader 321 * method if available. 322 */ 323 private static SchemaFactory newInstanceNoServiceLoader( 324 Class<?> providerClass 325 ) { 326 // Retain maximum compatibility if no security manager. 327 if (System.getSecurityManager() == null) { 328 return null; 329 } 330 try { 331 final Method creationMethod = 332 providerClass.getDeclaredMethod( 333 "newXMLSchemaFactoryNoServiceLoader" 334 ); 335 final int modifiers = creationMethod.getModifiers(); 336 337 // Do not call the method if it's not public static. 338 if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { 339 return null; 340 } 341 342 // Only calls "newXMLSchemaFactoryNoServiceLoader" if it's 343 // declared to return an instance of SchemaFactory. 344 final Class<?> returnType = creationMethod.getReturnType(); 345 if (SERVICE_CLASS.isAssignableFrom(returnType)) { 346 return SERVICE_CLASS.cast(creationMethod.invoke(null, (Object[])null)); 347 } else { 348 // Should not happen since 349 // XMLSchemaFactory.newXMLSchemaFactoryNoServiceLoader is 350 // declared to return XMLSchemaFactory. 351 throw new ClassCastException(returnType 352 + " cannot be cast to " + SERVICE_CLASS); 353 } 354 } catch(ClassCastException e) { 355 throw new SchemaFactoryConfigurationError(e.getMessage(), e); 356 } catch (NoSuchMethodException exc) { 357 return null; 358 } catch (Exception exc) { 359 return null; 360 } 361 } 362 363 // Call isSchemaLanguageSupported with initial context. 364 private boolean isSchemaLanguageSupportedBy(final SchemaFactory factory, 365 final String schemaLanguage, 366 AccessControlContext acc) { 367 return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { 368 public Boolean run() { 369 return factory.isSchemaLanguageSupported(schemaLanguage); 370 } 371 }, acc); 372 } 373 374 /** 375 * Finds a service provider subclass of SchemaFactory that supports the 376 * given schema language using the ServiceLoader. 377 * 378 * @param schemaLanguage The schema language for which we seek a factory. 379 * @return A SchemaFactory supporting the specified schema language, or null 380 * if none is found. 381 * @throws SchemaFactoryConfigurationError if a configuration error is found. 382 */ 383 private SchemaFactory findServiceProvider(final String schemaLanguage) { 384 assert schemaLanguage != null; 385 // store current context. 386 final AccessControlContext acc = AccessController.getContext(); 387 try { 388 return AccessController.doPrivileged(new PrivilegedAction<SchemaFactory>() { 389 public SchemaFactory run() { 390 final ServiceLoader<SchemaFactory> loader = 391 ServiceLoader.load(SERVICE_CLASS); 392 for (SchemaFactory factory : loader) { 393 // restore initial context to call 394 // factory.isSchemaLanguageSupported 395 if (isSchemaLanguageSupportedBy(factory, schemaLanguage, acc)) { 396 return factory; 397 } 398 } 399 return null; // no factory found. 400 } 401 }); 402 } catch (ServiceConfigurationError error) { 403 throw new SchemaFactoryConfigurationError( 404 "Provider for " + SERVICE_CLASS + " cannot be created", error); 405 } 406 } 407 408 private static final Class<SchemaFactory> SERVICE_CLASS = SchemaFactory.class; 409 410 411 private static String which( Class<?> clazz ) { 412 return which( clazz.getName(), clazz.getClassLoader() ); 413 } 414 415 /** 416 * <p>Search the specified classloader for the given classname.</p> 417 * 418 * @param classname the fully qualified name of the class to search for 419 * @param loader the classloader to search 420 * 421 * @return the source location of the resource, or null if it wasn't found 422 */ 423 private static String which(String classname, ClassLoader loader) { 424 425 String classnameAsResource = classname.replace('.', '/') + ".class"; 426 427 if( loader==null ) loader = ClassLoader.getSystemClassLoader(); 428 429 //URL it = loader.getResource(classnameAsResource); 430 URL it = ss.getResourceAsURL(loader, classnameAsResource); 431 if (it != null) { 432 return it.toString(); 433 } else { 434 return null; 435 } 436 } 437 }