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