1 /*
2 * $Id: FactoryFinder.java,v 1.37.4.1 2008/06/09 18:32:11 rlubke Exp $
3 */
4
5 /*
6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
7 *
8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
9 *
10 * The contents of this file are subject to the terms of either the GNU
11 * General Public License Version 2 only ("GPL") or the Common Development
12 * and Distribution License("CDDL") (collectively, the "License"). You
13 * may not use this file except in compliance with the License. You can obtain
14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
16 * language governing permissions and limitations under the License.
17 *
18 * When distributing the software, include this License Header Notice in each
19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
20 * Sun designates this particular file as subject to the "Classpath" exception
21 * as provided by Sun in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the License
23 * Header, with the fields enclosed by brackets [] replaced by your own
24 * identifying information: "Portions Copyrighted [year]
25 * [name of copyright owner]"
26 *
27 * Contributor(s):
28 *
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41 package javax.faces;
42
43
44 import java.io.BufferedReader;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.IOException;
48 import java.io.UnsupportedEncodingException;
49 import java.text.MessageFormat;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.ResourceBundle;
55 import java.util.Arrays;
56 import java.util.Enumeration;
57 import java.util.concurrent.ConcurrentMap;
58 import java.util.concurrent.ConcurrentHashMap;
59 import java.util.concurrent.Callable;
60 import java.util.concurrent.FutureTask;
61 import java.util.concurrent.Future;
62 import java.util.concurrent.CancellationException;
63 import java.util.concurrent.ExecutionException;
64 import java.util.concurrent.locks.ReentrantReadWriteLock;
65 import java.util.logging.Logger;
66 import java.util.logging.Level;
67 import java.lang.reflect.Constructor;
68 import java.net.URL;
69 import java.net.URLConnection;
70
71
72 /**
73 * <p><strong>FactoryFinder</strong> implements the standard discovery
74 * algorithm for all factory objects specified in the JavaServer Faces
75 * APIs. For a given factory class name, a corresponding implementation
76 * class is searched for based on the following algorithm. Items are
77 * listed in order of decreasing search precedence:</p> <ul>
78 * <p/>
79 * <li>If the JavaServer Faces configuration file bundled into the
80 * <code>WEB-INF</code> directory of the webapp contains a
81 * <code>factory</code> entry of the given factory class name, that
82 * factory is used.</li>
83 * <p/>
84 * <li>If the JavaServer Faces configuration files named by the
85 * <code>javax.faces.CONFIG_FILES</code>
86 * <code>ServletContext</code> init parameter contain any
87 * <code>factory</code> entries of the given factory class name, those
88 * factories are used, with the last one taking precedence.</li>
89 * <p/>
90 * <li>If there are any JavaServer Faces configuration files bundled
91 * into the <code>META-INF</code> directory of any jars on the
92 * <code>ServletContext</code>'s resource paths, the
93 * <code>factory</code> entries of the given factory class name in those
94 * files are used, with the last one taking precedence.</li>
95 * <p/>
96 * <li>If a <code>META-INF/services/{factory-class-name}</code> resource
97 * is visible to the web application class loader for the calling
98 * application (typically as a result of being present in the manifest
99 * of a JAR file), its first line is read and assumed to be the name of
100 * the factory implementation class to use.</li>
101 * <p/>
102 * <li>If none of the above steps yield a match, the JavaServer Faces
103 * implementation specific class is used.</li>
104 * <p/>
105 * </ul>
106 * <p/>
107 * <p>If any of the factories found on any of the steps above happen to
108 * have a one-argument constructor, with argument the type being the
109 * abstract factory class, that constructor is invoked, and the previous
110 * match is passed to the constructor. For example, say the container
111 * vendor provided an implementation of {@link
112 * javax.faces.context.FacesContextFactory}, and identified it in
113 * <code>META-INF/services/javax.faces.context.FacesContextFactory</code>
114 * in a jar on the webapp ClassLoader. Also say this implementation
115 * provided by the container vendor had a one argument constructor that
116 * took a <code>FacesContextFactory</code> instance. The
117 * <code>FactoryFinder</code> system would call that one-argument
118 * constructor, passing the implementation of
119 * <code>FacesContextFactory</code> provided by the JavaServer Faces
120 * implementation.</p>
121 * <p/>
122 * <p>If a Factory implementation does not provide a proper one-argument
123 * constructor, it must provide a zero-arguments constructor in order to
124 * be successfully instantiated.</p>
125 * <p/>
126 * <p>Once the name of the factory implementation class is located, the
127 * web application class loader for the calling application is requested
128 * to load this class, and a corresponding instance of the class will be
129 * created. A side effect of this rule is that each web application will
130 * receive its own instance of each factory class, whether the JavaServer
131 * Faces implementation is included within the web application or is made
132 * visible through the container's facilities for shared libraries.</p>
133 */
134
135 public final class FactoryFinder {
136
137 // ----------------------------------------------------------- Constructors
138
139
140 /**
141 * Package-private constructor to disable instantiation of this class.
142 */
143 FactoryFinder() {
144 }
145
146 // ----------------------------------------------------- Manifest Constants
147
148
149 /**
150 * <p>The property name for the
151 * {@link javax.faces.application.ApplicationFactory} class name.</p>
152 */
153 public final static String APPLICATION_FACTORY =
154 "javax.faces.application.ApplicationFactory";
155
156
157 /**
158 * <p>The property name for the
159 * {@link javax.faces.context.FacesContextFactory} class name.</p>
160 */
161 public final static String FACES_CONTEXT_FACTORY =
162 "javax.faces.context.FacesContextFactory";
163
164
165 /**
166 * <p>The property name for the
167 * {@link javax.faces.lifecycle.LifecycleFactory} class name.</p>
168 */
169 public final static String LIFECYCLE_FACTORY =
170 "javax.faces.lifecycle.LifecycleFactory";
171
172
173 /**
174 * <p>The property name for the
175 * {@link javax.faces.render.RenderKitFactory} class name.</p>
176 */
177 public final static String RENDER_KIT_FACTORY =
178 "javax.faces.render.RenderKitFactory";
179
180 // ------------------------------------------------------- Static Variables
181
182 private static final FactoryManagerCache FACTORIES_CACHE =
183 new FactoryManagerCache();
184
185
186 /**
187 * <p>The set of JavaServer Faces factory classes for which the factory
188 * discovery mechanism is supported.</p>
189 */
190 private static final String[] FACTORY_NAMES = {
191 APPLICATION_FACTORY,
192 FACES_CONTEXT_FACTORY,
193 LIFECYCLE_FACTORY,
194 RENDER_KIT_FACTORY
195 };
196
197 /**
198 * <p>Map of Class instances for the our factory names.</p>
199 */
200 private static Map<String, Class> factoryClasses = null;
201
202 private static final Logger LOGGER =
203 Logger.getLogger("javax.faces", "javax.faces.LogStrings");
204
205 // --------------------------------------------------------- Public Methods
206
207
208 /**
209 * <p>Create (if necessary) and return a per-web-application instance of
210 * the appropriate implementation class for the specified JavaServer Faces
211 * factory class, based on the discovery algorithm described in the
212 * class description.</p>
213 *
214 * @param factoryName Fully qualified name of the JavaServer Faces factory
215 * for which an implementation instance is requested
216 * @throws FacesException if the web application class loader
217 * cannot be identified
218 * @throws FacesException if an instance of the configured factory
219 * implementation class cannot be loaded
220 * @throws FacesException if an instance of the configured factory
221 * implementation class cannot be instantiated
222 * @throws IllegalArgumentException if <code>factoryName</code> does not
223 * identify a standard JavaServer Faces factory name
224 * @throws IllegalStateException if there is no configured factory
225 * implementation class for the specified factory name
226 * @throws NullPointerException if <code>factoryname</code>
227 * is null
228 */
229 public static Object getFactory(String factoryName)
230 throws FacesException {
231
232 validateFactoryName(factoryName);
233
234 // Identify the web application class loader
235 ClassLoader classLoader = getClassLoader();
236
237 FactoryManager manager =
238 FACTORIES_CACHE.getApplicationFactoryManager(classLoader);
239 return manager.getFactory(classLoader, factoryName);
240
241 }
242
243 /**
244 * <p>This method will store the argument
245 * <code>factoryName/implName</code> mapping in such a way that
246 * {@link #getFactory} will find this mapping when searching for a
247 * match.</p>
248 * <p/>
249 * <p>This method has no effect if <code>getFactory()</code> has
250 * already been called looking for a factory for this
251 * <code>factoryName</code>.</p>
252 * <p/>
253 * <p>This method can be used by implementations to store a factory
254 * mapping while parsing the Faces configuration file</p>
255 *
256 * @throws IllegalArgumentException if <code>factoryName</code> does not
257 * identify a standard JavaServer Faces factory name
258 * @throws NullPointerException if <code>factoryname</code>
259 * is null
260 */
261 public static void setFactory(String factoryName,
262 String implName) {
263
264 validateFactoryName(factoryName);
265
266 // Identify the web application class loader
267 ClassLoader classLoader = getClassLoader();
268
269 FactoryManager manager =
270 FACTORIES_CACHE.getApplicationFactoryManager(classLoader);
271 manager.addFactory(factoryName, implName);
272
273 }
274
275
276 /**
277 * <p>Release any references to factory instances associated with the
278 * class loader for the calling web application. This method should be
279 * called as apart of web application shutdown in a container where the
280 * JavaServer Faces API classes are part of the container itself, rather
281 * than being included inside the web application.</p>
282 *
283 * @throws FacesException if the web application class loader
284 * cannot be identified
285 */
286 public static void releaseFactories() throws FacesException {
287
288 // Identify the web application class loader
289 ClassLoader cl = getClassLoader();
290
291 FACTORIES_CACHE.removeApplicationFactoryManager(cl);
292
293 }
294
295
296 // -------------------------------------------------------- Private Methods
297
298
299 /**
300 * <p>Identify and return the class loader that is associated with the
301 * calling web application.</p>
302 *
303 * @throws FacesException if the web application class loader
304 * cannot be identified
305 */
306 private static ClassLoader getClassLoader() throws FacesException {
307
308 // J2EE 1.3 (and later) containers are required to make the
309 // web application class loader visible through the context
310 // class loader of the current thread.
311 ClassLoader cl = Thread.currentThread().getContextClassLoader();
312 if (cl == null) {
313 throw new FacesException("getContextClassLoader");
314 }
315 return (cl);
316
317 }
318
319
320 /**
321 * <p>Load and return an instance of the specified implementation
322 * class using the following algorithm.</p>
323 * <p/>
324 * <ol>
325 * <p/>
326 * <li><p>If the argument <code>implementations</code> list has
327 * more than one element, or exactly one element, interpret the
328 * last element in the list to be the fully qualified class name of
329 * a class implementing <code>factoryName</code>. Instantiate that
330 * class and save it for return. If the
331 * <code>implementations</code> list has only one element, skip
332 * this step.</p></li>
333 * <p/>
334 * <li><p>Look for a resource called
335 * <code>/META-INF/services/<factoryName></code>. If found,
336 * interpret it as a properties file, and read out the first entry.
337 * Interpret the first entry as a fully qualify class name of a
338 * class that implements <code>factoryName</code>. If we have an
339 * instantiated factory from the previous step <em>and</em> the
340 * implementing class has a one arg constructor of the type for
341 * <code>factoryName</code>, instantiate it, passing the
342 * instantiated factory from the previous step. If there is no one
343 * arg constructor, just instantiate the zero arg constructor. Save
344 * the newly instantiated factory for return, replacing the
345 * instantiated factory from the previous step.</p></li>
346 * <p/>
347 * <li><p>Treat each remaining element in the
348 * <code>implementations</code> list as a fully qualified class name
349 * of a class implementing <code>factoryName</code>. If the current
350 * element has a one arg constructor of the type for
351 * <code>factoryName</code>, instantiate it, passing the
352 * instantiated factory from the previous or step iteration. If
353 * there is no one arg constructor, just instantiate the zero arg
354 * constructor, replacing the instantiated factory from the previous
355 * step or iteration.</p></li>
356 * <p/>
357 * <li><p>Return the saved factory</p></li>
358 * <p/>
359 * </ol>
360 *
361 * @param classLoader Class loader for the web application that will
362 * be loading the implementation class
363 * @param implementations A List of implementations for a given
364 * factory class.
365 * @throws FacesException if the specified implementation class
366 * cannot be loaded
367 * @throws FacesException if an instance of the specified implementation
368 * class cannot be instantiated
369 */
370 private static Object getImplementationInstance(ClassLoader classLoader,
371 String factoryName,
372 List implementations)
373 throws FacesException {
374
375 Object result = null;
376 String curImplClass;
377 int len;
378
379 // step 1.
380 if (null != implementations &&
381 (1 < (len = implementations.size()) || 1 == len)) {
382 curImplClass = (String) implementations.remove(len - 1);
383 // since this is the hard coded implementation default,
384 // there is no preceding implementation, so don't bother
385 // with a non-zero-arg ctor.
386 result = getImplGivenPreviousImpl(classLoader, factoryName,
387 curImplClass, null);
388 }
389
390 // step 2.
391 List<String> fromServices = getImplNameFromServices(classLoader, factoryName);
392 if (fromServices != null) {
393 for (String name : fromServices) {
394 result = getImplGivenPreviousImpl(classLoader,
395 factoryName,
396 name,
397 result);
398 }
399 }
400
401 // step 3.
402 if (null != implementations) {
403 for (len = (implementations.size() - 1); 0 <= len; len--) {
404 curImplClass = (String) implementations.remove(len);
405 result = getImplGivenPreviousImpl(classLoader, factoryName,
406 curImplClass, result);
407 }
408 }
409
410 return result;
411
412 }
413
414
415 /**
416 * <p>Perform the logic to get the implementation class for the
417 * second step of {@link FactoryFinder#getImplementationInstance(ClassLoader, String, java.util.List)}.</p>
418 */
419 private static List<String> getImplNameFromServices(ClassLoader classLoader,
420 String factoryName) {
421
422 // Check for a services definition
423 List<String> result = null;
424 String resourceName = "META-INF/services/" + factoryName;
425 InputStream stream;
426 BufferedReader reader = null;
427 try {
428 Enumeration<URL> e = classLoader.getResources(resourceName);
429 while (e.hasMoreElements()) {
430 URL url = e.nextElement();
431 URLConnection conn = url.openConnection();
432 conn.setUseCaches(false);
433 stream = conn.getInputStream();
434 if (stream != null) {
435 // Deal with systems whose native encoding is possibly
436 // different from the way that the services entry was created
437 try {
438 reader =
439 new BufferedReader(new InputStreamReader(stream,
440 "UTF-8"));
441 if (result == null) {
442 result = new ArrayList<String>(3);
443 }
444 result.add(reader.readLine());
445 } catch (UnsupportedEncodingException uee) {
446 reader =
447 new BufferedReader(new InputStreamReader(stream));
448 } finally {
449 if (reader != null) {
450 reader.close();
451 reader = null;
452 }
453 if (stream != null) {
454 stream.close();
455 //noinspection UnusedAssignment
456 stream = null;
457 }
458 }
459
460 }
461 }
462 } catch (IOException e) {
463 if (LOGGER.isLoggable(Level.SEVERE)) {
464 LOGGER.log(Level.SEVERE,
465 e.toString(),
466 e);
467 }
468 } catch (SecurityException e) {
469 if (LOGGER.isLoggable(Level.SEVERE)) {
470 LOGGER.log(Level.SEVERE,
471 e.toString(),
472 e);
473 }
474 }
475 return result;
476
477 }
478
479
480 /**
481 * <p>Implement the decorator pattern for the factory
482 * implementation.</p>
483 * <p/>
484 * <p>If <code>previousImpl</code> is non-<code>null</code> and the
485 * class named by the argument <code>implName</code> has a one arg
486 * contstructor of type <code>factoryName</code>, instantiate it,
487 * passing previousImpl to the constructor.</p>
488 * <p/>
489 * <p>Otherwise, we just instantiate and return
490 * <code>implName</code>.</p>
491 *
492 * @param classLoader the ClassLoader from which to load the class
493 * @param factoryName the fully qualified class name of the factory.
494 * @param implName the fully qualified class name of a class that
495 * implements the factory.
496 * @param previousImpl if non-<code>null</code>, the factory
497 * instance to be passed to the constructor of the new factory.
498 */
499 private static Object getImplGivenPreviousImpl(ClassLoader classLoader,
500 String factoryName,
501 String implName,
502 Object previousImpl) {
503 Class clazz;
504 Class factoryClass = null;
505 Class[] getCtorArg;
506 Object[] newInstanceArgs = new Object[1];
507 Constructor ctor;
508 Object result = null;
509
510 // if we have a previousImpl and the appropriate one arg ctor.
511 if ((null != previousImpl) &&
512 (null != (factoryClass = getFactoryClass(factoryName)))) {
513 try {
514 clazz = Class.forName(implName, false, classLoader);
515 getCtorArg = new Class[1];
516 getCtorArg[0] = factoryClass;
517 ctor = clazz.getConstructor(getCtorArg);
518 newInstanceArgs[0] = previousImpl;
519 result = ctor.newInstance(newInstanceArgs);
520 }
521 catch (NoSuchMethodException nsme) {
522 // fall through to "zero-arg-ctor" case
523 factoryClass = null;
524 }
525 catch (Exception e) {
526 throw new FacesException(implName, e);
527 }
528 }
529 if (null == previousImpl || null == factoryClass) {
530 // we have either no previousImpl or no appropriate one arg
531 // ctor.
532 try {
533 clazz = Class.forName(implName, false, classLoader);
534 // since this is the hard coded implementation default,
535 // there is no preceding implementation, so don't bother
536 // with a non-zero-arg ctor.
537 result = clazz.newInstance();
538 } catch (Exception e) {
539 throw new FacesException(implName, e);
540 }
541 }
542 return result;
543
544 }
545
546
547 /**
548 * @return the <code>java.lang.Class</code> for the argument
549 * factory.
550 */
551 private static Class getFactoryClass(String factoryClassName) {
552
553 if (null == factoryClasses) {
554 factoryClasses = new HashMap<String, Class>(FACTORY_NAMES.length);
555 factoryClasses.put(APPLICATION_FACTORY,
556 javax.faces.application.ApplicationFactory.class);
557 factoryClasses.put(FACES_CONTEXT_FACTORY,
558 javax.faces.context.FacesContextFactory.class);
559 factoryClasses.put(LIFECYCLE_FACTORY,
560 javax.faces.lifecycle.LifecycleFactory.class);
561 factoryClasses.put(RENDER_KIT_FACTORY,
562 javax.faces.render.RenderKitFactory.class);
563 }
564 return factoryClasses.get(factoryClassName);
565
566 }
567
568
569 /**
570 * Ensure the provided factory name is valid.
571 */
572 private static void validateFactoryName(String factoryName) {
573
574 if (factoryName == null) {
575 throw new NullPointerException();
576 }
577 if (Arrays.binarySearch(FACTORY_NAMES, factoryName) < 0) {
578 throw new IllegalArgumentException(factoryName);
579 }
580
581 }
582
583
584 // ----------------------------------------------------------- Inner Classes
585
586
587 /**
588 * Managed the mappings between a web application and its configured
589 * factories.
590 */
591 private static final class FactoryManagerCache {
592
593 private ConcurrentMap<ClassLoader,Future<FactoryManager>> applicationMap =
594 new ConcurrentHashMap<ClassLoader, Future<FactoryManager>>();
595
596
597 // ------------------------------------------------------ Public Methods
598
599
600 private FactoryManager getApplicationFactoryManager(ClassLoader cl) {
601
602 while (true) {
603 Future<FactoryManager> factories = applicationMap.get(cl);
604 if (factories == null) {
605 Callable<FactoryManager> callable =
606 new Callable<FactoryManager>() {
607 public FactoryManager call()
608 throws Exception {
609 return new FactoryManager();
610 }
611 };
612
613 FutureTask<FactoryManager> ft =
614 new FutureTask<FactoryManager>(callable);
615 factories = applicationMap.putIfAbsent(cl, ft);
616 if (factories == null) {
617 factories = ft;
618 ft.run();
619 }
620 }
621
622 try {
623 return factories.get();
624 } catch (CancellationException ce) {
625 if (LOGGER.isLoggable(Level.FINEST)) {
626 LOGGER.log(Level.FINEST,
627 ce.toString(),
628 ce);
629 }
630 applicationMap.remove(cl);
631 } catch (InterruptedException ie) {
632 if (LOGGER.isLoggable(Level.FINEST)) {
633 LOGGER.log(Level.FINEST,
634 ie.toString(),
635 ie);
636 }
637 applicationMap.remove(cl);
638 } catch (ExecutionException ee) {
639 throw new FacesException(ee);
640 }
641
642 }
643
644 }
645
646
647 public void removeApplicationFactoryManager(ClassLoader cl) {
648
649 applicationMap.remove(cl);
650
651 }
652
653 } // END FactoryCache
654
655
656 /**
657 * Maintains the factories for a single web application.
658 */
659 private static final class FactoryManager {
660
661 private final Map<String,Object> factories;
662 private final ReentrantReadWriteLock lock;
663
664
665 // -------------------------------------------------------- Consturctors
666
667
668 public FactoryManager() {
669 factories = new HashMap<String,Object>();
670 for (String name : FACTORY_NAMES) {
671 factories.put(name, new ArrayList(4));
672 }
673 lock = new ReentrantReadWriteLock(true);
674 }
675
676
677 // ------------------------------------------------------ Public Methods
678
679
680 public void addFactory(String factoryName, String implementation) {
681
682 Object result = factories.get(factoryName);
683 lock.writeLock().lock();
684 try {
685 if (result instanceof List) {
686 TypedCollections.dynamicallyCastList((List) result, String.class).add(0, implementation);
687 }
688 } finally {
689 lock.writeLock().unlock();
690 }
691 }
692
693
694 public Object getFactory(ClassLoader cl, String factoryName) {
695
696 Object factoryOrList;
697 lock.readLock().lock();
698 try {
699 factoryOrList = factories.get(factoryName);
700 if (!(factoryOrList instanceof List)) {
701 return factoryOrList;
702 }
703 } finally {
704 lock.readLock().unlock();
705 }
706
707 // factory hasn't been constructed
708 lock.writeLock().lock();
709 try {
710 // double check the current value. Another thread
711 // may have completed the initialization by the time
712 // this thread entered this block
713 factoryOrList = factories.get(factoryName);
714 if (!(factoryOrList instanceof List)) {
715 return factoryOrList;
716 }
717 Object factory = getImplementationInstance(cl,
718 factoryName,
719 (List) factoryOrList);
720
721 if (factory == null) {
722 ResourceBundle rb = LOGGER.getResourceBundle();
723 String message = rb.getString("severe.no_factory");
724 message = MessageFormat.format(message, factoryName);
725 throw new IllegalStateException(message);
726 }
727
728 // Record and return the new instance
729 factories.put(factoryName, factory);
730 return (factory);
731 } finally {
732 lock.writeLock().unlock();
733 }
734 }
735
736 } // END FactoryManager
737
738
739 }