SchemaFactoryFinder.java revision 86acc043d3334651ee26c65467d78d6cefedd397
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 19package javax.xml.validation; 20 21import java.io.BufferedReader; 22import java.io.File; 23import java.io.FileInputStream; 24import java.io.IOException; 25import java.io.InputStream; 26import java.io.InputStreamReader; 27import java.net.URL; 28import java.util.Collections; 29import java.util.Enumeration; 30import java.util.Iterator; 31import java.util.Properties; 32import javax.xml.XMLConstants; 33import libcore.io.IoUtils; 34 35/** 36 * Implementation of {@link SchemaFactory#newInstance(String)}. 37 * 38 * @author <a href="Kohsuke.Kawaguchi@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 */ 42final 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) { 135 throw new NullPointerException("schemaLanguage == null"); 136 } 137 SchemaFactory f = _newFactory(schemaLanguage); 138 if (debug) { 139 if (f != null) { 140 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + schemaLanguage); 141 } else { 142 debugPrintln("unable to find a factory for " + schemaLanguage); 143 } 144 } 145 return f; 146 } 147 148 /** 149 * <p>Lookup a <code>SchemaFactory</code> for the given <code>schemaLanguage</code>.</p> 150 * 151 * @param schemaLanguage Schema language to lookup <code>SchemaFactory</code> for. 152 * 153 * @return <code>SchemaFactory</code> for the given <code>schemaLanguage</code>. 154 */ 155 private SchemaFactory _newFactory(String schemaLanguage) { 156 SchemaFactory sf; 157 String propertyName = SERVICE_CLASS.getName() + ":" + schemaLanguage; 158 159 // system property look up 160 try { 161 if (debug) debugPrintln("Looking up system property '"+propertyName+"'" ); 162 String r = System.getProperty(propertyName); 163 if (r != null && r.length() > 0) { 164 if (debug) debugPrintln("The value is '"+r+"'"); 165 sf = createInstance(r); 166 if(sf!=null) return sf; 167 } 168 else if (debug) { 169 debugPrintln("The property is undefined."); 170 } 171 } 172 // The VM ran out of memory or there was some other serious problem. Re-throw. 173 catch (VirtualMachineError vme) { 174 throw vme; 175 } 176 // ThreadDeath should always be re-thrown 177 catch (ThreadDeath td) { 178 throw td; 179 } 180 catch (Throwable t) { 181 if( debug ) { 182 debugPrintln("failed to look up system property '"+propertyName+"'" ); 183 t.printStackTrace(); 184 } 185 } 186 187 String javah = System.getProperty("java.home"); 188 String configFile = javah + File.separator + 189 "lib" + File.separator + "jaxp.properties"; 190 191 String factoryClassName = null ; 192 193 // try to read from $java.home/lib/jaxp.properties 194 try { 195 if(firstTime){ 196 synchronized(cacheProps){ 197 if(firstTime){ 198 File f=new File( configFile ); 199 firstTime = false; 200 if(f.exists()){ 201 if (debug) debugPrintln("Read properties file " + f); 202 cacheProps.load(new FileInputStream(f)); 203 } 204 } 205 } 206 } 207 factoryClassName = cacheProps.getProperty(propertyName); 208 if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties"); 209 210 if (factoryClassName != null) { 211 sf = createInstance(factoryClassName); 212 if(sf != null){ 213 return sf; 214 } 215 } 216 } catch (Exception ex) { 217 if (debug) { 218 ex.printStackTrace(); 219 } 220 } 221 222 // try META-INF/services files 223 for (URL resource : createServiceFileIterator()) { 224 if (debug) debugPrintln("looking into " + resource); 225 try { 226 sf = loadFromServicesFile(schemaLanguage,resource.toExternalForm(), 227 resource.openStream()); 228 if(sf!=null) return sf; 229 } catch(IOException e) { 230 if( debug ) { 231 debugPrintln("failed to read "+resource); 232 e.printStackTrace(); 233 } 234 } 235 } 236 237 // platform defaults 238 if (schemaLanguage.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI) || schemaLanguage.equals(W3C_XML_SCHEMA10_NS_URI)) { 239 if (debug) debugPrintln("attempting to use the platform default XML Schema 1.0 validator"); 240 return createInstance("org.apache.xerces.jaxp.validation.XMLSchemaFactory"); 241 } 242 else if (schemaLanguage.equals(W3C_XML_SCHEMA11_NS_URI)) { 243 if (debug) debugPrintln("attempting to use the platform default XML Schema 1.1 validator"); 244 return createInstance("org.apache.xerces.jaxp.validation.XMLSchema11Factory"); 245 } 246 247 if (debug) debugPrintln("all things were tried, but none was found. bailing out."); 248 return null; 249 } 250 251 /** 252 * <p>Creates an instance of the specified and returns it.</p> 253 * 254 * @param className 255 * fully qualified class name to be instantiated. 256 * 257 * @return null 258 * if it fails. Error messages will be printed by this method. 259 */ 260 SchemaFactory createInstance( String className ) { 261 try { 262 if (debug) debugPrintln("instantiating "+className); 263 Class clazz; 264 if( classLoader!=null ) 265 clazz = classLoader.loadClass(className); 266 else 267 clazz = Class.forName(className); 268 if(debug) debugPrintln("loaded it from "+which(clazz)); 269 Object o = clazz.newInstance(); 270 271 if( o instanceof SchemaFactory ) 272 return (SchemaFactory)o; 273 274 if (debug) debugPrintln(className+" is not assignable to "+SERVICE_CLASS.getName()); 275 } 276 // The VM ran out of memory or there was some other serious problem. Re-throw. 277 catch (VirtualMachineError vme) { 278 throw vme; 279 } 280 // ThreadDeath should always be re-thrown 281 catch (ThreadDeath td) { 282 throw td; 283 } 284 catch (Throwable t) { 285 debugPrintln("failed to instantiate "+className); 286 if(debug) t.printStackTrace(); 287 } 288 return null; 289 } 290 291 /** 292 * Returns an {@link Iterator} that enumerates all 293 * the META-INF/services files that we care. 294 */ 295 private Iterable<URL> createServiceFileIterator() { 296 if (classLoader == null) { 297 ClassLoader classLoader = SchemaFactoryFinder.class.getClassLoader(); 298 return Collections.singleton(classLoader.getResource(SERVICE_ID)); 299 } else { 300 try { 301 Enumeration<URL> e = classLoader.getResources(SERVICE_ID); 302 if (debug && !e.hasMoreElements()) { 303 debugPrintln("no "+SERVICE_ID+" file was found"); 304 } 305 306 // wrap it into an Iterator. 307 return Collections.list(e); 308 } catch (IOException e) { 309 if (debug) { 310 debugPrintln("failed to enumerate resources "+SERVICE_ID); 311 e.printStackTrace(); 312 } 313 return Collections.emptySet(); 314 } 315 } 316 } 317 318 /** Searches for a SchemaFactory for a given schema language in a META-INF/services file. */ 319 private SchemaFactory loadFromServicesFile(String schemaLanguage, String resourceName, InputStream in) { 320 321 if (debug) debugPrintln("Reading "+resourceName ); 322 323 // Read the service provider name in UTF-8 as specified in 324 // the jar spec. Unfortunately this fails in Microsoft 325 // VJ++, which does not implement the UTF-8 326 // encoding. Theoretically, we should simply let it fail in 327 // that case, since the JVM is obviously broken if it 328 // doesn't support such a basic standard. But since there 329 // are still some users attempting to use VJ++ for 330 // development, we have dropped in a fallback which makes a 331 // second attempt using the platform's default encoding. In 332 // VJ++ this is apparently ASCII, which is a subset of 333 // UTF-8... and since the strings we'll be reading here are 334 // also primarily limited to the 7-bit ASCII range (at 335 // least, in English versions), this should work well 336 // enough to keep us on the air until we're ready to 337 // officially decommit from VJ++. [Edited comment from 338 // jkesselm] 339 BufferedReader rd; 340 try { 341 rd = new BufferedReader(new InputStreamReader(in, "UTF-8"), DEFAULT_LINE_LENGTH); 342 } catch (java.io.UnsupportedEncodingException e) { 343 rd = new BufferedReader(new InputStreamReader(in), DEFAULT_LINE_LENGTH); 344 } 345 346 String factoryClassName = null; 347 SchemaFactory resultFactory = null; 348 // See spec for provider-configuration files: http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider%20Configuration%20File 349 while (true) { 350 try { 351 factoryClassName = rd.readLine(); 352 } catch (IOException x) { 353 // No provider found 354 break; 355 } 356 if (factoryClassName != null) { 357 // Ignore comments in the provider-configuration file 358 int hashIndex = factoryClassName.indexOf('#'); 359 if (hashIndex != -1) { 360 factoryClassName = factoryClassName.substring(0, hashIndex); 361 } 362 363 // Ignore leading and trailing whitespace 364 factoryClassName = factoryClassName.trim(); 365 366 // If there's no text left or if this was a blank line, go to the next one. 367 if (factoryClassName.length() == 0) { 368 continue; 369 } 370 371 try { 372 // Found the right SchemaFactory if its isSchemaLanguageSupported(schemaLanguage) method returns true. 373 SchemaFactory foundFactory = (SchemaFactory) createInstance(factoryClassName); 374 if (foundFactory.isSchemaLanguageSupported(schemaLanguage)) { 375 resultFactory = foundFactory; 376 break; 377 } 378 } 379 catch (Exception ignored) {} 380 } 381 else { 382 break; 383 } 384 } 385 386 IoUtils.closeQuietly(rd); 387 388 return resultFactory; 389 } 390 391 private static final Class SERVICE_CLASS = SchemaFactory.class; 392 private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName(); 393 394 private static String which( Class clazz ) { 395 return which( clazz.getName(), clazz.getClassLoader() ); 396 } 397 398 /** 399 * <p>Search the specified classloader for the given classname.</p> 400 * 401 * @param classname the fully qualified name of the class to search for 402 * @param loader the classloader to search 403 * 404 * @return the source location of the resource, or null if it wasn't found 405 */ 406 private static String which(String classname, ClassLoader loader) { 407 String classnameAsResource = classname.replace('.', '/') + ".class"; 408 409 if (loader == null) loader = ClassLoader.getSystemClassLoader(); 410 411 URL it = loader.getResource(classnameAsResource); 412 return it != null ? it.toString() : null; 413 } 414} 415