1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 // $Id: SchemaFactoryFinder.java 727367 2008-12-17 13:05:26Z mrglavas $ 18 19 package javax.xml.validation; 20 21 import java.io.BufferedReader; 22 import java.io.File; 23 import java.io.FileInputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.InputStreamReader; 27 import java.net.URL; 28 import java.util.Collections; 29 import java.util.Enumeration; 30 import java.util.Iterator; 31 import java.util.Properties; 32 import javax.xml.XMLConstants; 33 import libcore.io.IoUtils; 34 35 /** 36 * Implementation of {@link SchemaFactory#newInstance(String)}. 37 * 38 * @author <a href="Kohsuke.Kawaguchi (at) Sun.com">Kohsuke Kawaguchi</a> 39 * @version $Revision: 727367 $, $Date: 2008-12-17 05:05:26 -0800 (Wed, 17 Dec 2008) $ 40 * @since 1.5 41 */ 42 final class SchemaFactoryFinder { 43 44 /** XML Schema language identifiers. */ 45 private static final String W3C_XML_SCHEMA10_NS_URI = "http://www.w3.org/XML/XMLSchema/v1.0"; 46 private static final String W3C_XML_SCHEMA11_NS_URI = "http://www.w3.org/XML/XMLSchema/v1.1"; 47 48 /** debug support code. */ 49 private static boolean debug = false; 50 51 /** 52 * <p>Cache properties for performance.</p> 53 */ 54 private static Properties cacheProps = new Properties(); 55 56 /** 57 * <p>First time requires initialization overhead.</p> 58 */ 59 private static boolean firstTime = true; 60 61 /** 62 * Default columns per line. 63 */ 64 private static final int DEFAULT_LINE_LENGTH = 80; 65 66 static { 67 String val = System.getProperty("jaxp.debug"); 68 // Allow simply setting the prop to turn on debug 69 debug = val != null && (! "false".equals(val)); 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 if (classLoader == Thread.currentThread().getContextClassLoader()) { 108 debugPrintln("using thread context class loader ("+classLoader+") for search"); 109 return; 110 } 111 112 if (classLoader == ClassLoader.getSystemClassLoader()) { 113 debugPrintln("using system class loader ("+classLoader+") for search"); 114 return; 115 } 116 117 debugPrintln("using class loader (" + classLoader + ") for search"); 118 } 119 120 /** 121 * <p>Creates a new {@link SchemaFactory} object for the specified 122 * schema language.</p> 123 * 124 * @param schemaLanguage 125 * See {@link SchemaFactory Schema Language} table in <code>SchemaFactory</code> 126 * for the list of available schema languages. 127 * 128 * @return <code>null</code> if the callee fails to create one. 129 * 130 * @throws NullPointerException 131 * If the <tt>schemaLanguage</tt> parameter is null. 132 */ 133 public SchemaFactory newFactory(String schemaLanguage) { 134 if(schemaLanguage==null) throw new NullPointerException(); 135 SchemaFactory f = _newFactory(schemaLanguage); 136 if (debug) { 137 if (f != null) { 138 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + schemaLanguage); 139 } else { 140 debugPrintln("unable to find a factory for " + schemaLanguage); 141 } 142 } 143 return f; 144 } 145 146 /** 147 * <p>Lookup a <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.</p> 148 * 149 * @param schemaLanguage Schema language to lookup <code>SchemaFactory</code> for. 150 * 151 * @return <code>SchemaFactory</code> for the given <code>schemaLanguage</code>. 152 */ 153 private SchemaFactory _newFactory(String schemaLanguage) { 154 SchemaFactory sf; 155 String propertyName = SERVICE_CLASS.getName() + ":" + schemaLanguage; 156 157 // system property look up 158 try { 159 if (debug) debugPrintln("Looking up system property '"+propertyName+"'" ); 160 String r = System.getProperty(propertyName); 161 if (r != null && r.length() > 0) { 162 if (debug) debugPrintln("The value is '"+r+"'"); 163 sf = createInstance(r); 164 if(sf!=null) return sf; 165 } 166 else if (debug) { 167 debugPrintln("The property is undefined."); 168 } 169 } 170 // The VM ran out of memory or there was some other serious problem. Re-throw. 171 catch (VirtualMachineError vme) { 172 throw vme; 173 } 174 // ThreadDeath should always be re-thrown 175 catch (ThreadDeath td) { 176 throw td; 177 } 178 catch (Throwable t) { 179 if( debug ) { 180 debugPrintln("failed to look up system property '"+propertyName+"'" ); 181 t.printStackTrace(); 182 } 183 } 184 185 String javah = System.getProperty("java.home"); 186 String configFile = javah + File.separator + 187 "lib" + File.separator + "jaxp.properties"; 188 189 String factoryClassName = null ; 190 191 // try to read from $java.home/lib/jaxp.properties 192 try { 193 if(firstTime){ 194 synchronized(cacheProps){ 195 if(firstTime){ 196 File f=new File( configFile ); 197 firstTime = false; 198 if(f.exists()){ 199 if (debug) debugPrintln("Read properties file " + f); 200 cacheProps.load(new FileInputStream(f)); 201 } 202 } 203 } 204 } 205 factoryClassName = cacheProps.getProperty(propertyName); 206 if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties"); 207 208 if (factoryClassName != null) { 209 sf = createInstance(factoryClassName); 210 if(sf != null){ 211 return sf; 212 } 213 } 214 } catch (Exception ex) { 215 if (debug) { 216 ex.printStackTrace(); 217 } 218 } 219 220 // try META-INF/services files 221 for (URL resource : createServiceFileIterator()) { 222 if (debug) debugPrintln("looking into " + resource); 223 try { 224 sf = loadFromServicesFile(schemaLanguage,resource.toExternalForm(), 225 resource.openStream()); 226 if(sf!=null) return sf; 227 } catch(IOException e) { 228 if( debug ) { 229 debugPrintln("failed to read "+resource); 230 e.printStackTrace(); 231 } 232 } 233 } 234 235 // platform defaults 236 if (schemaLanguage.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI) || schemaLanguage.equals(W3C_XML_SCHEMA10_NS_URI)) { 237 if (debug) debugPrintln("attempting to use the platform default XML Schema 1.0 validator"); 238 return createInstance("org.apache.xerces.jaxp.validation.XMLSchemaFactory"); 239 } 240 else if (schemaLanguage.equals(W3C_XML_SCHEMA11_NS_URI)) { 241 if (debug) debugPrintln("attempting to use the platform default XML Schema 1.1 validator"); 242 return createInstance("org.apache.xerces.jaxp.validation.XMLSchema11Factory"); 243 } 244 245 if (debug) debugPrintln("all things were tried, but none was found. bailing out."); 246 return null; 247 } 248 249 /** 250 * <p>Creates an instance of the specified and returns it.</p> 251 * 252 * @param className 253 * fully qualified class name to be instantiated. 254 * 255 * @return null 256 * if it fails. Error messages will be printed by this method. 257 */ 258 SchemaFactory createInstance( String className ) { 259 try { 260 if (debug) debugPrintln("instantiating "+className); 261 Class clazz; 262 if( classLoader!=null ) 263 clazz = classLoader.loadClass(className); 264 else 265 clazz = Class.forName(className); 266 if(debug) debugPrintln("loaded it from "+which(clazz)); 267 Object o = clazz.newInstance(); 268 269 if( o instanceof SchemaFactory ) 270 return (SchemaFactory)o; 271 272 if (debug) debugPrintln(className+" is not assignable to "+SERVICE_CLASS.getName()); 273 } 274 // The VM ran out of memory or there was some other serious problem. Re-throw. 275 catch (VirtualMachineError vme) { 276 throw vme; 277 } 278 // ThreadDeath should always be re-thrown 279 catch (ThreadDeath td) { 280 throw td; 281 } 282 catch (Throwable t) { 283 debugPrintln("failed to instantiate "+className); 284 if(debug) t.printStackTrace(); 285 } 286 return null; 287 } 288 289 /** 290 * Returns an {@link Iterator} that enumerates all 291 * the META-INF/services files that we care. 292 */ 293 private Iterable<URL> createServiceFileIterator() { 294 if (classLoader == null) { 295 ClassLoader classLoader = SchemaFactoryFinder.class.getClassLoader(); 296 return Collections.singleton(classLoader.getResource(SERVICE_ID)); 297 } else { 298 try { 299 Enumeration<URL> e = classLoader.getResources(SERVICE_ID); 300 if (debug && !e.hasMoreElements()) { 301 debugPrintln("no "+SERVICE_ID+" file was found"); 302 } 303 304 // wrap it into an Iterator. 305 return Collections.list(e); 306 } catch (IOException e) { 307 if (debug) { 308 debugPrintln("failed to enumerate resources "+SERVICE_ID); 309 e.printStackTrace(); 310 } 311 return Collections.emptySet(); 312 } 313 } 314 } 315 316 /** Searches for a SchemaFactory for a given schema language in a META-INF/services file. */ 317 private SchemaFactory loadFromServicesFile(String schemaLanguage, String resourceName, InputStream in) { 318 319 if (debug) debugPrintln("Reading "+resourceName ); 320 321 // Read the service provider name in UTF-8 as specified in 322 // the jar spec. Unfortunately this fails in Microsoft 323 // VJ++, which does not implement the UTF-8 324 // encoding. Theoretically, we should simply let it fail in 325 // that case, since the JVM is obviously broken if it 326 // doesn't support such a basic standard. But since there 327 // are still some users attempting to use VJ++ for 328 // development, we have dropped in a fallback which makes a 329 // second attempt using the platform's default encoding. In 330 // VJ++ this is apparently ASCII, which is a subset of 331 // UTF-8... and since the strings we'll be reading here are 332 // also primarily limited to the 7-bit ASCII range (at 333 // least, in English versions), this should work well 334 // enough to keep us on the air until we're ready to 335 // officially decommit from VJ++. [Edited comment from 336 // jkesselm] 337 BufferedReader rd; 338 try { 339 rd = new BufferedReader(new InputStreamReader(in, "UTF-8"), DEFAULT_LINE_LENGTH); 340 } catch (java.io.UnsupportedEncodingException e) { 341 rd = new BufferedReader(new InputStreamReader(in), DEFAULT_LINE_LENGTH); 342 } 343 344 String factoryClassName = null; 345 SchemaFactory resultFactory = null; 346 // See spec for provider-configuration files: http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider%20Configuration%20File 347 while (true) { 348 try { 349 factoryClassName = rd.readLine(); 350 } catch (IOException x) { 351 // No provider found 352 break; 353 } 354 if (factoryClassName != null) { 355 // Ignore comments in the provider-configuration file 356 int hashIndex = factoryClassName.indexOf('#'); 357 if (hashIndex != -1) { 358 factoryClassName = factoryClassName.substring(0, hashIndex); 359 } 360 361 // Ignore leading and trailing whitespace 362 factoryClassName = factoryClassName.trim(); 363 364 // If there's no text left or if this was a blank line, go to the next one. 365 if (factoryClassName.length() == 0) { 366 continue; 367 } 368 369 try { 370 // Found the right SchemaFactory if its isSchemaLanguageSupported(schemaLanguage) method returns true. 371 SchemaFactory foundFactory = (SchemaFactory) createInstance(factoryClassName); 372 if (foundFactory.isSchemaLanguageSupported(schemaLanguage)) { 373 resultFactory = foundFactory; 374 break; 375 } 376 } 377 catch (Exception ignored) {} 378 } 379 else { 380 break; 381 } 382 } 383 384 IoUtils.closeQuietly(rd); 385 386 return resultFactory; 387 } 388 389 private static final Class SERVICE_CLASS = SchemaFactory.class; 390 private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName(); 391 392 private static String which( Class clazz ) { 393 return which( clazz.getName(), clazz.getClassLoader() ); 394 } 395 396 /** 397 * <p>Search the specified classloader for the given classname.</p> 398 * 399 * @param classname the fully qualified name of the class to search for 400 * @param loader the classloader to search 401 * 402 * @return the source location of the resource, or null if it wasn't found 403 */ 404 private static String which(String classname, ClassLoader loader) { 405 String classnameAsResource = classname.replace('.', '/') + ".class"; 406 407 if (loader == null) loader = ClassLoader.getSystemClassLoader(); 408 409 URL it = loader.getResource(classnameAsResource); 410 return it != null ? it.toString() : null; 411 } 412 } 413