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