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