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: XPathFactoryFinder.java 670432 2008-06-23 02:02:08Z mrglavas $ 18 19 package javax.xml.xpath; 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.validation.SchemaFactory; 33 import libcore.io.IoUtils; 34 35 /** 36 * Implementation of {@link XPathFactory#newInstance(String)}. 37 * 38 * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> 39 * @version $Revision: 670432 $, $Date: 2008-06-22 19:02:08 -0700 (Sun, 22 Jun 2008) $ 40 * @since 1.5 41 */ 42 final class XPathFactoryFinder { 43 44 /** debug support code. */ 45 private static boolean debug = false; 46 47 /** 48 * Default columns per line. 49 */ 50 private static final int DEFAULT_LINE_LENGTH = 80; 51 52 static { 53 String val = System.getProperty("jaxp.debug"); 54 // Allow simply setting the prop to turn on debug 55 debug = val != null && (! "false".equals(val)); 56 } 57 58 /** 59 * <p>Cache properties for performance. Use a static class to avoid double-checked 60 * locking.</p> 61 */ 62 private static class CacheHolder { 63 64 private static Properties cacheProps = new Properties(); 65 66 static { 67 String javah = System.getProperty("java.home"); 68 String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties"; 69 File f = new File(configFile); 70 if (f.exists()) { 71 if (debug) debugPrintln("Read properties file " + f); try(FileInputStream inputStream = new FileInputStream(f))72 try (FileInputStream inputStream = new FileInputStream(f)) { 73 cacheProps.load(inputStream); 74 } catch (Exception ex) { 75 if (debug) { 76 ex.printStackTrace(); 77 } 78 } 79 } 80 } 81 } 82 83 /** 84 * <p>Conditional debug printing.</p> 85 * 86 * @param msg to print 87 */ debugPrintln(String msg)88 private static void debugPrintln(String msg) { 89 if (debug) { 90 System.err.println("JAXP: " + msg); 91 } 92 } 93 94 /** 95 * <p><code>ClassLoader</code> to use to find <code>SchemaFactory</code>.</p> 96 */ 97 private final ClassLoader classLoader; 98 99 /** 100 * <p>Constructor that specifies <code>ClassLoader</code> to use 101 * to find <code>SchemaFactory</code>.</p> 102 * 103 * @param loader 104 * to be used to load resource, {@link SchemaFactory}, and 105 * {@code SchemaFactoryLoader} implementations during 106 * the resolution process. 107 * If this parameter is null, the default system class loader 108 * will be used. 109 */ XPathFactoryFinder(ClassLoader loader)110 public XPathFactoryFinder(ClassLoader loader) { 111 this.classLoader = loader; 112 if (debug) { 113 debugDisplayClassLoader(); 114 } 115 } 116 debugDisplayClassLoader()117 private void debugDisplayClassLoader() { 118 if (classLoader == Thread.currentThread().getContextClassLoader()) { 119 debugPrintln("using thread context class loader (" + classLoader + ") for search"); 120 return; 121 } 122 123 if (classLoader==ClassLoader.getSystemClassLoader()) { 124 debugPrintln("using system class loader (" + classLoader + ") for search"); 125 return; 126 } 127 128 debugPrintln("using class loader (" + classLoader + ") for search"); 129 } 130 131 /** 132 * <p>Creates a new {@link XPathFactory} object for the specified 133 * schema language.</p> 134 * 135 * @param uri 136 * Identifies the underlying object model. 137 * 138 * @return <code>null</code> if the callee fails to create one. 139 * 140 * @throws NullPointerException 141 * If the parameter is null. 142 */ newFactory(String uri)143 public XPathFactory newFactory(String uri) { 144 if (uri == null) { 145 throw new NullPointerException("uri == null"); 146 } 147 XPathFactory f = _newFactory(uri); 148 if (debug) { 149 if (f != null) { 150 debugPrintln("factory '" + f.getClass().getName() + "' was found for " + uri); 151 } else { 152 debugPrintln("unable to find a factory for " + uri); 153 } 154 } 155 return f; 156 } 157 158 /** 159 * <p>Lookup a {@link XPathFactory} for the given object model.</p> 160 * 161 * @param uri identifies the object model. 162 */ _newFactory(String uri)163 private XPathFactory _newFactory(String uri) { 164 XPathFactory xpf; 165 String propertyName = SERVICE_CLASS.getName() + ":" + uri; 166 167 // system property look up 168 try { 169 if (debug) debugPrintln("Looking up system property '"+propertyName+"'" ); 170 String r = System.getProperty(propertyName); 171 if (r != null && r.length() > 0) { 172 if (debug) debugPrintln("The value is '"+r+"'"); 173 xpf = createInstance(r); 174 if(xpf!=null) return xpf; 175 } else if (debug) { 176 debugPrintln("The property is undefined."); 177 } 178 } catch (Exception e) { 179 e.printStackTrace(); 180 } 181 182 // try to read from $java.home/lib/jaxp.properties 183 try { 184 String factoryClassName = CacheHolder.cacheProps.getProperty(propertyName); 185 if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties"); 186 187 if (factoryClassName != null) { 188 xpf = createInstance(factoryClassName); 189 if(xpf != null){ 190 return xpf; 191 } 192 } 193 } catch (Exception ex) { 194 if (debug) { 195 ex.printStackTrace(); 196 } 197 } 198 199 // try META-INF/services files 200 for (URL resource : createServiceFileIterator()) { 201 if (debug) debugPrintln("looking into " + resource); 202 try { 203 xpf = loadFromServicesFile(uri, resource.toExternalForm(), resource.openStream()); 204 if(xpf!=null) return xpf; 205 } catch(IOException e) { 206 if( debug ) { 207 debugPrintln("failed to read "+resource); 208 e.printStackTrace(); 209 } 210 } 211 } 212 213 // platform default 214 if(uri.equals(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) { 215 if (debug) debugPrintln("attempting to use the platform default W3C DOM XPath lib"); 216 return createInstance("org.apache.xpath.jaxp.XPathFactoryImpl"); 217 } 218 219 if (debug) debugPrintln("all things were tried, but none was found. bailing out."); 220 return null; 221 } 222 223 /** 224 * <p>Creates an instance of the specified and returns it.</p> 225 * 226 * @param className 227 * fully qualified class name to be instantiated. 228 * 229 * @return null 230 * if it fails. Error messages will be printed by this method. 231 */ createInstance( String className )232 XPathFactory createInstance( String className ) { 233 try { 234 if (debug) debugPrintln("instantiating "+className); 235 Class clazz; 236 if( classLoader!=null ) 237 clazz = classLoader.loadClass(className); 238 else 239 clazz = Class.forName(className); 240 if(debug) debugPrintln("loaded it from "+which(clazz)); 241 Object o = clazz.newInstance(); 242 243 if( o instanceof XPathFactory ) 244 return (XPathFactory)o; 245 246 if (debug) debugPrintln(className+" is not assignable to "+SERVICE_CLASS.getName()); 247 } 248 // The VM ran out of memory or there was some other serious problem. Re-throw. 249 catch (VirtualMachineError vme) { 250 throw vme; 251 } 252 // ThreadDeath should always be re-thrown 253 catch (ThreadDeath td) { 254 throw td; 255 } 256 catch (Throwable t) { 257 if (debug) { 258 debugPrintln("failed to instantiate "+className); 259 t.printStackTrace(); 260 } 261 } 262 return null; 263 } 264 265 /** Searches for a XPathFactory for a given uri in a META-INF/services file. */ loadFromServicesFile(String uri, String resourceName, InputStream in)266 private XPathFactory loadFromServicesFile(String uri, String resourceName, InputStream in) { 267 268 if (debug) debugPrintln("Reading " + resourceName ); 269 270 BufferedReader rd; 271 try { 272 rd = new BufferedReader(new InputStreamReader(in, "UTF-8"), DEFAULT_LINE_LENGTH); 273 } catch (java.io.UnsupportedEncodingException e) { 274 rd = new BufferedReader(new InputStreamReader(in), DEFAULT_LINE_LENGTH); 275 } 276 277 String factoryClassName; 278 XPathFactory resultFactory = null; 279 // See spec for provider-configuration files: http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider%20Configuration%20File 280 while (true) { 281 try { 282 factoryClassName = rd.readLine(); 283 } catch (IOException x) { 284 // No provider found 285 break; 286 } 287 if (factoryClassName != null) { 288 // Ignore comments in the provider-configuration file 289 int hashIndex = factoryClassName.indexOf('#'); 290 if (hashIndex != -1) { 291 factoryClassName = factoryClassName.substring(0, hashIndex); 292 } 293 294 // Ignore leading and trailing whitespace 295 factoryClassName = factoryClassName.trim(); 296 297 // If there's no text left or if this was a blank line, go to the next one. 298 if (factoryClassName.length() == 0) { 299 continue; 300 } 301 302 try { 303 // Found the right XPathFactory if its isObjectModelSupported(String uri) method returns true. 304 XPathFactory foundFactory = createInstance(factoryClassName); 305 if (foundFactory.isObjectModelSupported(uri)) { 306 resultFactory = foundFactory; 307 break; 308 } 309 } catch (Exception ignored) { 310 } 311 } 312 else { 313 break; 314 } 315 } 316 317 IoUtils.closeQuietly(rd); 318 319 return resultFactory; 320 } 321 322 /** 323 * Returns an {@link Iterator} that enumerates all 324 * the META-INF/services files that we care. 325 */ createServiceFileIterator()326 private Iterable<URL> createServiceFileIterator() { 327 if (classLoader == null) { 328 URL resource = XPathFactoryFinder.class.getClassLoader().getResource(SERVICE_ID); 329 return Collections.singleton(resource); 330 } else { 331 try { 332 Enumeration<URL> e = classLoader.getResources(SERVICE_ID); 333 if (debug && !e.hasMoreElements()) { 334 debugPrintln("no "+SERVICE_ID+" file was found"); 335 } 336 337 return Collections.list(e); 338 } catch (IOException e) { 339 if (debug) { 340 debugPrintln("failed to enumerate resources "+SERVICE_ID); 341 e.printStackTrace(); 342 } 343 return Collections.emptySet(); 344 } 345 } 346 } 347 348 private static final Class SERVICE_CLASS = XPathFactory.class; 349 private static final String SERVICE_ID = "META-INF/services/" + SERVICE_CLASS.getName(); 350 which( Class clazz )351 private static String which( Class clazz ) { 352 return which( clazz.getName(), clazz.getClassLoader() ); 353 } 354 355 /** 356 * <p>Search the specified classloader for the given classname.</p> 357 * 358 * @param classname the fully qualified name of the class to search for 359 * @param loader the classloader to search 360 * 361 * @return the source location of the resource, or null if it wasn't found 362 */ which(String classname, ClassLoader loader)363 private static String which(String classname, ClassLoader loader) { 364 String classnameAsResource = classname.replace('.', '/') + ".class"; 365 if (loader==null) loader = ClassLoader.getSystemClassLoader(); 366 367 URL it = loader.getResource(classnameAsResource); 368 return it != null ? it.toString() : null; 369 } 370 } 371