001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.html.dom;
019:
020: import java.io.InputStream;
021: import java.io.IOException;
022: import java.io.File;
023: import java.io.FileInputStream;
024:
025: import java.util.Properties;
026: import java.io.BufferedReader;
027: import java.io.InputStreamReader;
028:
029: /**
030: * This class is duplicated for each JAXP subpackage so keep it in sync.
031: * It is package private and therefore is not exposed as part of the JAXP
032: * API.
033: * <p>
034: * This code is designed to implement the JAXP 1.1 spec pluggability
035: * feature and is designed to run on JDK version 1.1 and
036: * later, and to compile on JDK 1.2 and onward.
037: * The code also runs both as part of an unbundled jar file and
038: * when bundled as part of the JDK.
039: * <p>
040: *
041: * @xerces.internal
042: *
043: * @version $Id: ObjectFactory.java 558589 2007-07-23 02:41:55Z mrglavas $
044: */
045: final class ObjectFactory {
046:
047: //
048: // Constants
049: //
050:
051: // name of default properties file to look for in JDK's jre/lib directory
052: private static final String DEFAULT_PROPERTIES_FILENAME = "xerces.properties";
053:
054: /** Set to true for debugging */
055: private static final boolean DEBUG = false;
056:
057: /**
058: * Default columns per line.
059: */
060: private static final int DEFAULT_LINE_LENGTH = 80;
061:
062: /** cache the contents of the xerces.properties file.
063: * Until an attempt has been made to read this file, this will
064: * be null; if the file does not exist or we encounter some other error
065: * during the read, this will be empty.
066: */
067: private static Properties fXercesProperties = null;
068:
069: /***
070: * Cache the time stamp of the xerces.properties file so
071: * that we know if it's been modified and can invalidate
072: * the cache when necessary.
073: */
074: private static long fLastModified = -1;
075:
076: //
077: // static methods
078: //
079:
080: /**
081: * Finds the implementation Class object in the specified order. The
082: * specified order is the following:
083: * <ol>
084: * <li>query the system property using <code>System.getProperty</code>
085: * <li>read <code>META-INF/services/<i>factoryId</i></code> file
086: * <li>use fallback classname
087: * </ol>
088: *
089: * @return Class object of factory, never null
090: *
091: * @param factoryId Name of the factory to find, same as
092: * a property name
093: * @param fallbackClassName Implementation class name, if nothing else
094: * is found. Use null to mean no fallback.
095: *
096: * @exception ObjectFactory.ConfigurationError
097: */
098: static Object createObject(String factoryId,
099: String fallbackClassName) throws ConfigurationError {
100: return createObject(factoryId, null, fallbackClassName);
101: } // createObject(String,String):Object
102:
103: /**
104: * Finds the implementation Class object in the specified order. The
105: * specified order is the following:
106: * <ol>
107: * <li>query the system property using <code>System.getProperty</code>
108: * <li>read <code>$java.home/lib/<i>propertiesFilename</i></code> file
109: * <li>read <code>META-INF/services/<i>factoryId</i></code> file
110: * <li>use fallback classname
111: * </ol>
112: *
113: * @return Class object of factory, never null
114: *
115: * @param factoryId Name of the factory to find, same as
116: * a property name
117: * @param propertiesFilename The filename in the $java.home/lib directory
118: * of the properties file. If none specified,
119: * ${java.home}/lib/xerces.properties will be used.
120: * @param fallbackClassName Implementation class name, if nothing else
121: * is found. Use null to mean no fallback.
122: *
123: * @exception ObjectFactory.ConfigurationError
124: */
125: static Object createObject(String factoryId,
126: String propertiesFilename, String fallbackClassName)
127: throws ConfigurationError {
128: if (DEBUG)
129: debugPrintln("debug is on");
130:
131: ClassLoader cl = findClassLoader();
132:
133: // Use the system property first
134: try {
135: String systemProp = SecuritySupport
136: .getSystemProperty(factoryId);
137: if (systemProp != null) {
138: if (DEBUG)
139: debugPrintln("found system property, value="
140: + systemProp);
141: return newInstance(systemProp, cl, true);
142: }
143: } catch (SecurityException se) {
144: // Ignore and continue w/ next location
145: }
146:
147: // Try to read from propertiesFilename, or $java.home/lib/xerces.properties
148: String factoryClassName = null;
149: // no properties file name specified; use $JAVA_HOME/lib/xerces.properties:
150: if (propertiesFilename == null) {
151: File propertiesFile = null;
152: boolean propertiesFileExists = false;
153: try {
154: String javah = SecuritySupport
155: .getSystemProperty("java.home");
156: propertiesFilename = javah + File.separator + "lib"
157: + File.separator + DEFAULT_PROPERTIES_FILENAME;
158: propertiesFile = new File(propertiesFilename);
159: propertiesFileExists = SecuritySupport
160: .getFileExists(propertiesFile);
161: } catch (SecurityException e) {
162: // try again...
163: fLastModified = -1;
164: fXercesProperties = null;
165: }
166:
167: synchronized (ObjectFactory.class) {
168: boolean loadProperties = false;
169: FileInputStream fis = null;
170: try {
171: // file existed last time
172: if (fLastModified >= 0) {
173: if (propertiesFileExists
174: && (fLastModified < (fLastModified = SecuritySupport
175: .getLastModified(propertiesFile)))) {
176: loadProperties = true;
177: } else {
178: // file has stopped existing...
179: if (!propertiesFileExists) {
180: fLastModified = -1;
181: fXercesProperties = null;
182: } // else, file wasn't modified!
183: }
184: } else {
185: // file has started to exist:
186: if (propertiesFileExists) {
187: loadProperties = true;
188: fLastModified = SecuritySupport
189: .getLastModified(propertiesFile);
190: } // else, nothing's changed
191: }
192: if (loadProperties) {
193: // must never have attempted to read xerces.properties before (or it's outdeated)
194: fXercesProperties = new Properties();
195: fis = SecuritySupport
196: .getFileInputStream(propertiesFile);
197: fXercesProperties.load(fis);
198: }
199: } catch (Exception x) {
200: fXercesProperties = null;
201: fLastModified = -1;
202: // assert(x instanceof FileNotFoundException
203: // || x instanceof SecurityException)
204: // In both cases, ignore and continue w/ next location
205: } finally {
206: // try to close the input stream if one was opened.
207: if (fis != null) {
208: try {
209: fis.close();
210: }
211: // Ignore the exception.
212: catch (IOException exc) {
213: }
214: }
215: }
216: }
217: if (fXercesProperties != null) {
218: factoryClassName = fXercesProperties
219: .getProperty(factoryId);
220: }
221: } else {
222: FileInputStream fis = null;
223: try {
224: fis = SecuritySupport.getFileInputStream(new File(
225: propertiesFilename));
226: Properties props = new Properties();
227: props.load(fis);
228: factoryClassName = props.getProperty(factoryId);
229: } catch (Exception x) {
230: // assert(x instanceof FileNotFoundException
231: // || x instanceof SecurityException)
232: // In both cases, ignore and continue w/ next location
233: } finally {
234: // try to close the input stream if one was opened.
235: if (fis != null) {
236: try {
237: fis.close();
238: }
239: // Ignore the exception.
240: catch (IOException exc) {
241: }
242: }
243: }
244: }
245: if (factoryClassName != null) {
246: if (DEBUG)
247: debugPrintln("found in " + propertiesFilename
248: + ", value=" + factoryClassName);
249: return newInstance(factoryClassName, cl, true);
250: }
251:
252: // Try Jar Service Provider Mechanism
253: Object provider = findJarServiceProvider(factoryId);
254: if (provider != null) {
255: return provider;
256: }
257:
258: if (fallbackClassName == null) {
259: throw new ConfigurationError("Provider for " + factoryId
260: + " cannot be found", null);
261: }
262:
263: if (DEBUG)
264: debugPrintln("using fallback, value=" + fallbackClassName);
265: return newInstance(fallbackClassName, cl, true);
266: } // createObject(String,String,String):Object
267:
268: //
269: // Private static methods
270: //
271:
272: /** Prints a message to standard error if debugging is enabled. */
273: private static void debugPrintln(String msg) {
274: if (DEBUG) {
275: System.err.println("JAXP: " + msg);
276: }
277: } // debugPrintln(String)
278:
279: /**
280: * Figure out which ClassLoader to use. For JDK 1.2 and later use
281: * the context ClassLoader.
282: */
283: static ClassLoader findClassLoader() throws ConfigurationError {
284: // Figure out which ClassLoader to use for loading the provider
285: // class. If there is a Context ClassLoader then use it.
286: ClassLoader context = SecuritySupport.getContextClassLoader();
287: ClassLoader system = SecuritySupport.getSystemClassLoader();
288:
289: ClassLoader chain = system;
290: while (true) {
291: if (context == chain) {
292: // Assert: we are on JDK 1.1 or we have no Context ClassLoader
293: // or any Context ClassLoader in chain of system classloader
294: // (including extension ClassLoader) so extend to widest
295: // ClassLoader (always look in system ClassLoader if Xerces
296: // is in boot/extension/system classpath and in current
297: // ClassLoader otherwise); normal classloaders delegate
298: // back to system ClassLoader first so this widening doesn't
299: // change the fact that context ClassLoader will be consulted
300: ClassLoader current = ObjectFactory.class
301: .getClassLoader();
302:
303: chain = system;
304: while (true) {
305: if (current == chain) {
306: // Assert: Current ClassLoader in chain of
307: // boot/extension/system ClassLoaders
308: return system;
309: }
310: if (chain == null) {
311: break;
312: }
313: chain = SecuritySupport.getParentClassLoader(chain);
314: }
315:
316: // Assert: Current ClassLoader not in chain of
317: // boot/extension/system ClassLoaders
318: return current;
319: }
320:
321: if (chain == null) {
322: // boot ClassLoader reached
323: break;
324: }
325:
326: // Check for any extension ClassLoaders in chain up to
327: // boot ClassLoader
328: chain = SecuritySupport.getParentClassLoader(chain);
329: }
330: ;
331:
332: // Assert: Context ClassLoader not in chain of
333: // boot/extension/system ClassLoaders
334: return context;
335: } // findClassLoader():ClassLoader
336:
337: /**
338: * Create an instance of a class using the specified ClassLoader
339: */
340: static Object newInstance(String className, ClassLoader cl,
341: boolean doFallback) throws ConfigurationError {
342: // assert(className != null);
343: try {
344: Class providerClass = findProviderClass(className, cl,
345: doFallback);
346: Object instance = providerClass.newInstance();
347: if (DEBUG)
348: debugPrintln("created new instance of " + providerClass
349: + " using ClassLoader: " + cl);
350: return instance;
351: } catch (ClassNotFoundException x) {
352: throw new ConfigurationError("Provider " + className
353: + " not found", x);
354: } catch (Exception x) {
355: throw new ConfigurationError("Provider " + className
356: + " could not be instantiated: " + x, x);
357: }
358: }
359:
360: /**
361: * Find a Class using the specified ClassLoader
362: */
363: static Class findProviderClass(String className, ClassLoader cl,
364: boolean doFallback) throws ClassNotFoundException,
365: ConfigurationError {
366: //throw security exception if the calling thread is not allowed to access the package
367: //restrict the access to package as speicified in java.security policy
368: SecurityManager security = System.getSecurityManager();
369: if (security != null) {
370: final int lastDot = className.lastIndexOf(".");
371: String packageName = className;
372: if (lastDot != -1)
373: packageName = className.substring(0, lastDot);
374: security.checkPackageAccess(packageName);
375: }
376: Class providerClass;
377: if (cl == null) {
378: // XXX Use the bootstrap ClassLoader. There is no way to
379: // load a class using the bootstrap ClassLoader that works
380: // in both JDK 1.1 and Java 2. However, this should still
381: // work b/c the following should be true:
382: //
383: // (cl == null) iff current ClassLoader == null
384: //
385: // Thus Class.forName(String) will use the current
386: // ClassLoader which will be the bootstrap ClassLoader.
387: providerClass = Class.forName(className);
388: } else {
389: try {
390: providerClass = cl.loadClass(className);
391: } catch (ClassNotFoundException x) {
392: if (doFallback) {
393: // Fall back to current classloader
394: ClassLoader current = ObjectFactory.class
395: .getClassLoader();
396: if (current == null) {
397: providerClass = Class.forName(className);
398: } else if (cl != current) {
399: cl = current;
400: providerClass = cl.loadClass(className);
401: } else {
402: throw x;
403: }
404: } else {
405: throw x;
406: }
407: }
408: }
409:
410: return providerClass;
411: }
412:
413: /*
414: * Try to find provider using Jar Service Provider Mechanism
415: *
416: * @return instance of provider class if found or null
417: */
418: private static Object findJarServiceProvider(String factoryId)
419: throws ConfigurationError {
420: String serviceId = "META-INF/services/" + factoryId;
421: InputStream is = null;
422:
423: // First try the Context ClassLoader
424: ClassLoader cl = findClassLoader();
425:
426: is = SecuritySupport.getResourceAsStream(cl, serviceId);
427:
428: // If no provider found then try the current ClassLoader
429: if (is == null) {
430: ClassLoader current = ObjectFactory.class.getClassLoader();
431: if (cl != current) {
432: cl = current;
433: is = SecuritySupport.getResourceAsStream(cl, serviceId);
434: }
435: }
436:
437: if (is == null) {
438: // No provider found
439: return null;
440: }
441:
442: if (DEBUG)
443: debugPrintln("found jar resource=" + serviceId
444: + " using ClassLoader: " + cl);
445:
446: // Read the service provider name in UTF-8 as specified in
447: // the jar spec. Unfortunately this fails in Microsoft
448: // VJ++, which does not implement the UTF-8
449: // encoding. Theoretically, we should simply let it fail in
450: // that case, since the JVM is obviously broken if it
451: // doesn't support such a basic standard. But since there
452: // are still some users attempting to use VJ++ for
453: // development, we have dropped in a fallback which makes a
454: // second attempt using the platform's default encoding. In
455: // VJ++ this is apparently ASCII, which is a subset of
456: // UTF-8... and since the strings we'll be reading here are
457: // also primarily limited to the 7-bit ASCII range (at
458: // least, in English versions), this should work well
459: // enough to keep us on the air until we're ready to
460: // officially decommit from VJ++. [Edited comment from
461: // jkesselm]
462: BufferedReader rd;
463: try {
464: rd = new BufferedReader(new InputStreamReader(is, "UTF-8"),
465: DEFAULT_LINE_LENGTH);
466: } catch (java.io.UnsupportedEncodingException e) {
467: rd = new BufferedReader(new InputStreamReader(is),
468: DEFAULT_LINE_LENGTH);
469: }
470:
471: String factoryClassName = null;
472: try {
473: // XXX Does not handle all possible input as specified by the
474: // Jar Service Provider specification
475: factoryClassName = rd.readLine();
476: } catch (IOException x) {
477: // No provider found
478: return null;
479: } finally {
480: try {
481: // try to close the reader.
482: rd.close();
483: }
484: // Ignore the exception.
485: catch (IOException exc) {
486: }
487: }
488:
489: if (factoryClassName != null && !"".equals(factoryClassName)) {
490: if (DEBUG)
491: debugPrintln("found in resource, value="
492: + factoryClassName);
493:
494: // Note: here we do not want to fall back to the current
495: // ClassLoader because we want to avoid the case where the
496: // resource file was found using one ClassLoader and the
497: // provider class was instantiated using a different one.
498: return newInstance(factoryClassName, cl, false);
499: }
500:
501: // No provider found
502: return null;
503: }
504:
505: //
506: // Classes
507: //
508:
509: /**
510: * A configuration error.
511: */
512: static final class ConfigurationError extends Error {
513:
514: /** Serialization version. */
515: static final long serialVersionUID = 2646822752226280048L;
516:
517: //
518: // Data
519: //
520:
521: /** Exception. */
522: private Exception exception;
523:
524: //
525: // Constructors
526: //
527:
528: /**
529: * Construct a new instance with the specified detail string and
530: * exception.
531: */
532: ConfigurationError(String msg, Exception x) {
533: super (msg);
534: this .exception = x;
535: } // <init>(String,Exception)
536:
537: //
538: // methods
539: //
540:
541: /** Returns the exception associated to this error. */
542: Exception getException() {
543: return exception;
544: } // getException():Exception
545:
546: } // class ConfigurationError
547:
548: } // class ObjectFactory
|