001 /*
002 * Copyright 2004-2005 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.xml.xpath;
027
028 import java.io.BufferedReader;
029 import java.io.File;
030 import java.io.FileInputStream;
031 import java.io.IOException;
032 import java.io.InputStream;
033 import java.io.InputStreamReader;
034 import java.lang.reflect.Method;
035 import java.lang.reflect.InvocationTargetException;
036 import java.net.URL;
037 import java.util.ArrayList;
038 import java.util.Enumeration;
039 import java.util.Iterator;
040 import java.util.NoSuchElementException;
041 import java.util.Properties;
042
043 /**
044 * Implementation of {@link XPathFactory#newInstance(String)}.
045 *
046 * @author <a href="Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
047 * @version $Revision: 1.4 $, $Date: 2005/10/06 05:39:24 $
048 * @since 1.5
049 */
050 class XPathFactoryFinder {
051
052 private static SecuritySupport ss = new SecuritySupport();
053 /** debug support code. */
054 private static boolean debug = false;
055 static {
056 // Use try/catch block to support applets
057 try {
058 debug = ss.getSystemProperty("jaxp.debug") != null;
059 } catch (Exception _) {
060 debug = false;
061 }
062 }
063
064 /**
065 * <p>Cache properties for performance.</p>
066 */
067 private static Properties cacheProps = new Properties();
068
069 /**
070 * <p>First time requires initialization overhead.</p>
071 */
072 private static boolean firstTime = true;
073
074 /**
075 * <p>Conditional debug printing.</p>
076 *
077 * @param msg to print
078 */
079 private static void debugPrintln(String msg) {
080 if (debug) {
081 System.err.println("JAXP: " + msg);
082 }
083 }
084
085 /**
086 * <p><code>ClassLoader</code> to use to find <code>XPathFactory</code>.</p>
087 */
088 private final ClassLoader classLoader;
089
090 /**
091 * <p>Constructor that specifies <code>ClassLoader</code> to use
092 * to find <code>XPathFactory</code>.</p>
093 *
094 * @param loader
095 * to be used to load resource, {@link XPathFactory}, and
096 * {@link SchemaFactoryLoader} implementations during
097 * the resolution process.
098 * If this parameter is null, the default system class loader
099 * will be used.
100 */
101 public XPathFactoryFinder(ClassLoader loader) {
102 this .classLoader = loader;
103 if (debug) {
104 debugDisplayClassLoader();
105 }
106 }
107
108 private void debugDisplayClassLoader() {
109 try {
110 if (classLoader == ss.getContextClassLoader()) {
111 debugPrintln("using thread context class loader ("
112 + classLoader + ") for search");
113 return;
114 }
115 } catch (Throwable _) {
116 ; // getContextClassLoader() undefined in JDK1.1
117 }
118
119 if (classLoader == ClassLoader.getSystemClassLoader()) {
120 debugPrintln("using system class loader (" + classLoader
121 + ") for search");
122 return;
123 }
124
125 debugPrintln("using class loader (" + classLoader
126 + ") for search");
127 }
128
129 /**
130 * <p>Creates a new {@link XPathFactory} object for the specified
131 * schema language.</p>
132 *
133 * @param uri
134 * Identifies the underlying object model.
135 *
136 * @return <code>null</code> if the callee fails to create one.
137 *
138 * @throws NullPointerException
139 * If the parameter is null.
140 */
141 public XPathFactory newFactory(String uri) {
142 if (uri == null)
143 throw new NullPointerException();
144 XPathFactory f = _newFactory(uri);
145 if (f != null) {
146 debugPrintln("factory '" + f.getClass().getName()
147 + "' was found for " + uri);
148 } else {
149 debugPrintln("unable to find a factory for " + uri);
150 }
151 return f;
152 }
153
154 /**
155 * <p>Lookup a {@link XPathFactory} for the given object model.</p>
156 *
157 * @param uri identifies the object model.
158 *
159 * @return {@link XPathFactory} for the given object model.
160 */
161 private XPathFactory _newFactory(String uri) {
162 XPathFactory xpathFactory;
163
164 String propertyName = SERVICE_CLASS.getName() + ":" + uri;
165
166 // system property look up
167 try {
168 debugPrintln("Looking up system property '" + propertyName
169 + "'");
170 String r = ss.getSystemProperty(propertyName);
171 if (r != null) {
172 debugPrintln("The value is '" + r + "'");
173 xpathFactory = createInstance(r);
174 if (xpathFactory != null)
175 return xpathFactory;
176 } else
177 debugPrintln("The property is undefined.");
178 } catch (Throwable t) {
179 if (debug) {
180 debugPrintln("failed to look up system property '"
181 + propertyName + "'");
182 t.printStackTrace();
183 }
184 }
185
186 String javah = ss.getSystemProperty("java.home");
187 String configFile = javah + File.separator + "lib"
188 + File.separator + "jaxp.properties";
189
190 String factoryClassName = null;
191
192 // try to read from $java.home/lib/jaxp.properties
193 try {
194 if (firstTime) {
195 synchronized (cacheProps) {
196 if (firstTime) {
197 File f = new File(configFile);
198 firstTime = false;
199 if (ss.doesFileExist(f)) {
200 debugPrintln("Read properties file " + f);
201 cacheProps.load(ss.getFileInputStream(f));
202 }
203 }
204 }
205 }
206 factoryClassName = cacheProps.getProperty(propertyName);
207 debugPrintln("found " + factoryClassName
208 + " in $java.home/jaxp.properties");
209
210 if (factoryClassName != null) {
211 xpathFactory = createInstance(factoryClassName);
212 if (xpathFactory != null) {
213 return xpathFactory;
214 }
215 }
216 } catch (Exception ex) {
217 if (debug) {
218 ex.printStackTrace();
219 }
220 }
221
222 // try META-INF/services files
223 Iterator sitr = createServiceFileIterator();
224 while (sitr.hasNext()) {
225 URL resource = (URL) sitr.next();
226 debugPrintln("looking into " + resource);
227 try {
228 xpathFactory = loadFromService(uri, resource
229 .toExternalForm(), ss
230 .getURLInputStream(resource));
231 if (xpathFactory != null) {
232 return xpathFactory;
233 }
234 } catch (IOException e) {
235 if (debug) {
236 debugPrintln("failed to read " + resource);
237 e.printStackTrace();
238 }
239 }
240 }
241
242 // platform default
243 if (uri.equals(XPathFactory.DEFAULT_OBJECT_MODEL_URI)) {
244 debugPrintln("attempting to use the platform default W3C DOM XPath lib");
245 return createInstance("com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl");
246 }
247
248 debugPrintln("all things were tried, but none was found. bailing out.");
249 return null;
250 }
251
252 /** <p>Create class using appropriate ClassLoader.</p>
253 *
254 * @param className Name of class to create.
255 * @return Created class or <code>null</code>.
256 */
257 private Class createClass(String className) {
258 Class clazz;
259
260 // use approprite ClassLoader
261 try {
262 if (classLoader != null) {
263 clazz = classLoader.loadClass(className);
264 } else {
265 clazz = Class.forName(className);
266 }
267 } catch (Throwable t) {
268 if (debug)
269 t.printStackTrace();
270 return null;
271 }
272
273 return clazz;
274 }
275
276 /**
277 * <p>Creates an instance of the specified and returns it.</p>
278 *
279 * @param className
280 * fully qualified class name to be instanciated.
281 *
282 * @return null
283 * if it fails. Error messages will be printed by this method.
284 */
285 XPathFactory createInstance(String className) {
286 XPathFactory xPathFactory = null;
287
288 debugPrintln("createInstance(" + className + ")");
289
290 // get Class from className
291 Class clazz = createClass(className);
292 if (clazz == null) {
293 debugPrintln("failed to getClass(" + className + ")");
294 return null;
295 }
296 debugPrintln("loaded " + className + " from " + which(clazz));
297
298 // instantiate Class as a XPathFactory
299 try {
300 xPathFactory = (XPathFactory) clazz.newInstance();
301 } catch (ClassCastException classCastException) {
302 debugPrintln("could not instantiate " + clazz.getName());
303 if (debug) {
304 classCastException.printStackTrace();
305 }
306 return null;
307 } catch (IllegalAccessException illegalAccessException) {
308 debugPrintln("could not instantiate " + clazz.getName());
309 if (debug) {
310 illegalAccessException.printStackTrace();
311 }
312 return null;
313 } catch (InstantiationException instantiationException) {
314 debugPrintln("could not instantiate " + clazz.getName());
315 if (debug) {
316 instantiationException.printStackTrace();
317 }
318 return null;
319 }
320
321 return xPathFactory;
322 }
323
324 /**
325 * <p>Look up a value in a property file.</p>
326 *
327 * <p>Set <code>debug</code> to <code>true</code> to trace property evaluation.</p>
328 *
329 * @param objectModel URI of object model to support.
330 * @param inputName Name of <code>InputStream</code>.
331 * @param in <code>InputStream</code> of properties.
332 *
333 * @return <code>XPathFactory</code> as determined by <code>keyName</code> value or <code>null</code> if there was an error.
334 *
335 * @throws IOException If IO error reading from <code>in</code>.
336 */
337 private XPathFactory loadFromService(String objectModel,
338 String inputName, InputStream in) throws IOException {
339
340 XPathFactory xPathFactory = null;
341 final Class[] stringClassArray = { "".getClass() };
342 final Object[] objectModelObjectArray = { objectModel };
343 final String isObjectModelSupportedMethod = "isObjectModelSupported";
344
345 debugPrintln("Reading " + inputName);
346
347 // read from InputStream until a match is found
348 BufferedReader configFile = new BufferedReader(
349 new InputStreamReader(in));
350 String line = null;
351 while ((line = configFile.readLine()) != null) {
352 // '#' is comment char
353 int comment = line.indexOf("#");
354 switch (comment) {
355 case -1:
356 break; // no comment
357 case 0:
358 line = "";
359 break; // entire line is a comment
360 default:
361 line = line.substring(0, comment);
362 break; // trim comment
363 }
364
365 // trim whitespace
366 line = line.trim();
367
368 // any content left on line?
369 if (line.length() == 0) {
370 continue;
371 }
372
373 // line content is now the name of the class
374 Class clazz = createClass(line);
375 if (clazz == null) {
376 continue;
377 }
378
379 // create an instance of the Class
380 try {
381 xPathFactory = (XPathFactory) clazz.newInstance();
382 } catch (ClassCastException classCastExcpetion) {
383 xPathFactory = null;
384 continue;
385 } catch (InstantiationException instantiationException) {
386 xPathFactory = null;
387 continue;
388 } catch (IllegalAccessException illegalAccessException) {
389 xPathFactory = null;
390 continue;
391 }
392
393 // does this Class support desired object model?
394 try {
395 Method isObjectModelSupported = clazz.getMethod(
396 isObjectModelSupportedMethod, stringClassArray);
397 Boolean supported = (Boolean) isObjectModelSupported
398 .invoke(xPathFactory, objectModelObjectArray);
399 if (supported.booleanValue()) {
400 break;
401 }
402
403 } catch (NoSuchMethodException noSuchMethodException) {
404
405 } catch (IllegalAccessException illegalAccessException) {
406
407 } catch (InvocationTargetException invocationTargetException) {
408
409 }
410 xPathFactory = null;
411 }
412
413 // clean up
414 configFile.close();
415
416 // return new instance of XPathFactory or null
417 return xPathFactory;
418 }
419
420 /** Iterator that lazily computes one value and returns it. */
421 private static abstract class SingleIterator implements Iterator {
422 private boolean seen = false;
423
424 public final void remove() {
425 throw new UnsupportedOperationException();
426 }
427
428 public final boolean hasNext() {
429 return !seen;
430 }
431
432 public final Object next() {
433 if (seen)
434 throw new NoSuchElementException();
435 seen = true;
436 return value();
437 }
438
439 protected abstract Object value();
440 }
441
442 /**
443 * Looks up a value in a property file
444 * while producing all sorts of debug messages.
445 *
446 * @return null
447 * if there was an error.
448 */
449 private XPathFactory loadFromProperty(String keyName,
450 String resourceName, InputStream in) throws IOException {
451 debugPrintln("Reading " + resourceName);
452
453 Properties props = new Properties();
454 props.load(in);
455 in.close();
456 String factoryClassName = props.getProperty(keyName);
457 if (factoryClassName != null) {
458 debugPrintln("found " + keyName + " = " + factoryClassName);
459 return createInstance(factoryClassName);
460 } else {
461 debugPrintln(keyName + " is not in the property file");
462 return null;
463 }
464 }
465
466 /**
467 * Returns an {@link Iterator} that enumerates all
468 * the META-INF/services files that we care.
469 */
470 private Iterator createServiceFileIterator() {
471 if (classLoader == null) {
472 return new SingleIterator() {
473 protected Object value() {
474 ClassLoader classLoader = XPathFactoryFinder.class
475 .getClassLoader();
476 return ss.getResourceAsURL(classLoader, SERVICE_ID);
477 //return (ClassLoader.getSystemResource( SERVICE_ID ));
478 }
479 };
480 } else {
481 try {
482 //final Enumeration e = classLoader.getResources(SERVICE_ID);
483 final Enumeration e = ss.getResources(classLoader,
484 SERVICE_ID);
485 if (!e.hasMoreElements()) {
486 debugPrintln("no " + SERVICE_ID + " file was found");
487 }
488
489 // wrap it into an Iterator.
490 return new Iterator() {
491 public void remove() {
492 throw new UnsupportedOperationException();
493 }
494
495 public boolean hasNext() {
496 return e.hasMoreElements();
497 }
498
499 public Object next() {
500 return e.nextElement();
501 }
502 };
503 } catch (IOException e) {
504 debugPrintln("failed to enumerate resources "
505 + SERVICE_ID);
506 if (debug)
507 e.printStackTrace();
508 return new ArrayList().iterator(); // empty iterator
509 }
510 }
511 }
512
513 private static final Class SERVICE_CLASS = XPathFactory.class;
514 private static final String SERVICE_ID = "META-INF/services/"
515 + SERVICE_CLASS.getName();
516
517 private static String which(Class clazz) {
518 return which(clazz.getName(), clazz.getClassLoader());
519 }
520
521 /**
522 * <p>Search the specified classloader for the given classname.</p>
523 *
524 * @param classname the fully qualified name of the class to search for
525 * @param loader the classloader to search
526 *
527 * @return the source location of the resource, or null if it wasn't found
528 */
529 private static String which(String classname, ClassLoader loader) {
530
531 String classnameAsResource = classname.replace('.', '/')
532 + ".class";
533
534 if (loader == null)
535 loader = ClassLoader.getSystemClassLoader();
536
537 //URL it = loader.getResource(classnameAsResource);
538 URL it = ss.getResourceAsURL(loader, classnameAsResource);
539 if (it != null) {
540 return it.toString();
541 } else {
542 return null;
543 }
544 }
545 }
|