View Javadoc

1   package org.apache.turbine.services.factory;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.ObjectOutputStream;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  
30  import org.apache.commons.configuration.Configuration;
31  
32  import org.apache.turbine.services.InitializationException;
33  import org.apache.turbine.services.TurbineBaseService;
34  import org.apache.turbine.util.TurbineException;
35  import org.apache.turbine.util.pool.ObjectInputStreamForContext;
36  
37  /***
38   * The Factory Service instantiates objects using specified
39   * class loaders. If none is specified, the default one
40   * will be used.
41   *
42   * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
43   * @version $Id: TurbineFactoryService.java 534527 2007-05-02 16:10:59Z tv $
44   */
45  public class TurbineFactoryService
46          extends TurbineBaseService
47          implements FactoryService
48  {
49      /***
50       * The property specifying a set of additional class loaders.
51       */
52      public static final String CLASS_LOADERS = "class.loaders";
53  
54      /***
55       * The property prefix specifying additional object factories.
56       */
57      public static final String OBJECT_FACTORY = "factory.";
58  
59      /***
60       * Primitive classes for reflection of constructors.
61       */
62      private static HashMap primitiveClasses;
63  
64      {
65          primitiveClasses = new HashMap(8);
66          primitiveClasses.put(Boolean.TYPE.toString(), Boolean.TYPE);
67          primitiveClasses.put(Character.TYPE.toString(), Character.TYPE);
68          primitiveClasses.put(Byte.TYPE.toString(), Byte.TYPE);
69          primitiveClasses.put(Short.TYPE.toString(), Short.TYPE);
70          primitiveClasses.put(Integer.TYPE.toString(), Integer.TYPE);
71          primitiveClasses.put(Long.TYPE.toString(), Long.TYPE);
72          primitiveClasses.put(Float.TYPE.toString(), Float.TYPE);
73          primitiveClasses.put(Double.TYPE.toString(), Double.TYPE);
74      }
75  
76      /***
77       * Additional class loaders.
78       */
79      private ArrayList classLoaders = new ArrayList();
80  
81      /***
82       * Customized object factories.
83       */
84      private HashMap objectFactories = new HashMap();
85  
86      /***
87       * Gets the class of a primitive type.
88       *
89       * @param type a primitive type.
90       * @return the corresponding class, or null.
91       */
92      protected static Class getPrimitiveClass(String type)
93      {
94          return (Class) primitiveClasses.get(type);
95      }
96  
97      /***
98       * Constructs a Factory Service.
99       */
100     public TurbineFactoryService()
101     {
102     }
103 
104     /***
105      * Initializes the service by loading default class loaders
106      * and customized object factories.
107      *
108      * @throws InitializationException if initialization fails.
109      */
110     public void init() throws InitializationException
111     {
112         Configuration conf = getConfiguration();
113         if (conf != null)
114         {
115             List loaders = conf.getList(CLASS_LOADERS);
116             if (loaders != null)
117             {
118                 for (int i = 0; i < loaders.size(); i++)
119                 {
120                     try
121                     {
122                         classLoaders.add(
123                                 loadClass((String) loaders.get(i)).newInstance());
124                     }
125                     catch (Exception x)
126                     {
127                         throw new InitializationException(
128                                 "No such class loader '" +
129                                 (String) loaders.get(i) +
130                                 "' for TurbineFactoryService", x);
131                     }
132                 }
133             }
134 
135             String key,factory;
136             for (Iterator i = conf.getKeys(OBJECT_FACTORY); i.hasNext();)
137             {
138                 key = (String) i.next();
139                 factory = conf.getString(key);
140 
141                 /*
142                  * Store the factory to the table as a string and
143                  * instantiate it by using the service when needed.
144                  */
145                 objectFactories.put(
146                         key.substring(OBJECT_FACTORY.length()), factory);
147             }
148         }
149         setInit(true);
150     }
151 
152     /***
153      * Gets an instance of a named class.
154      *
155      * @param className the name of the class.
156      * @return the instance.
157      * @throws TurbineException if instantiation fails.
158      */
159     public Object getInstance(String className)
160             throws TurbineException
161     {
162         if (className == null)
163         {
164             throw new TurbineException(
165                     new NullPointerException("String className"));
166         }
167 
168         Factory factory = getFactory(className);
169         if (factory == null)
170         {
171             Class clazz;
172             try
173             {
174                 clazz = loadClass(className);
175             }
176             catch (ClassNotFoundException x)
177             {
178                 throw new TurbineException(
179                         "Instantiation failed for class " + className, x);
180             }
181             return getInstance(clazz);
182         }
183         else
184         {
185             return factory.getInstance();
186         }
187     }
188 
189     /***
190      * Gets an instance of a named class using a specified class loader.
191      *
192      * <p>Class loaders are supported only if the isLoaderSupported
193      * method returns true. Otherwise the loader parameter is ignored.
194      *
195      * @param className the name of the class.
196      * @param loader the class loader.
197      * @return the instance.
198      * @throws TurbineException if instantiation fails.
199      */
200     public Object getInstance(String className,
201             ClassLoader loader)
202             throws TurbineException
203     {
204         if (className == null)
205         {
206             throw new TurbineException(
207                     new NullPointerException("String className"));
208         }
209 
210         Factory factory = getFactory(className);
211         if (factory == null)
212         {
213             if (loader != null)
214             {
215                 Class clazz;
216                 try
217                 {
218                     clazz = loadClass(className, loader);
219                 }
220                 catch (ClassNotFoundException x)
221                 {
222                     throw new TurbineException(
223                             "Instantiation failed for class " + className, x);
224                 }
225                 return getInstance(clazz);
226             }
227             else
228             {
229                 return getInstance(className);
230             }
231         }
232         else
233         {
234             return factory.getInstance(loader);
235         }
236     }
237 
238     /***
239      * Gets an instance of a named class.
240      * Parameters for its constructor are given as an array of objects,
241      * primitive types must be wrapped with a corresponding class.
242      *
243      * @param className the name of the class.
244      * @param params an array containing the parameters of the constructor.
245      * @param signature an array containing the signature of the constructor.
246      * @return the instance.
247      * @throws TurbineException if instantiation fails.
248      */
249     public Object getInstance(String className,
250             Object[] params,
251             String[] signature)
252             throws TurbineException
253     {
254         if (className == null)
255         {
256             throw new TurbineException(
257                     new NullPointerException("String className"));
258         }
259 
260         Factory factory = getFactory(className);
261         if (factory == null)
262         {
263             Class clazz;
264             try
265             {
266                 clazz = loadClass(className);
267             }
268             catch (ClassNotFoundException x)
269             {
270                 throw new TurbineException(
271                         "Instantiation failed for class " + className, x);
272             }
273             return getInstance(clazz, params, signature);
274         }
275         else
276         {
277             return factory.getInstance(params, signature);
278         }
279     }
280 
281     /***
282      * Gets an instance of a named class using a specified class loader.
283      * Parameters for its constructor are given as an array of objects,
284      * primitive types must be wrapped with a corresponding class.
285      *
286      * <p>Class loaders are supported only if the isLoaderSupported
287      * method returns true. Otherwise the loader parameter is ignored.
288      *
289      * @param className the name of the class.
290      * @param loader the class loader.
291      * @param params an array containing the parameters of the constructor.
292      * @param signature an array containing the signature of the constructor.
293      * @return the instance.
294      * @throws TurbineException if instantiation fails.
295      */
296     public Object getInstance(String className,
297             ClassLoader loader,
298             Object[] params,
299             String[] signature)
300             throws TurbineException
301     {
302         if (className == null)
303         {
304             throw new TurbineException(
305                     new NullPointerException("String className"));
306         }
307 
308         Factory factory = getFactory(className);
309         if (factory == null)
310         {
311             if (loader != null)
312             {
313                 Class clazz;
314                 try
315                 {
316                     clazz = loadClass(className, loader);
317                 }
318                 catch (ClassNotFoundException x)
319                 {
320                     throw new TurbineException(
321                             "Instantiation failed for class " + className, x);
322                 }
323                 return getInstance(clazz, params, signature);
324             }
325             else
326             {
327                 return getInstance(className, params, signature);
328             }
329         }
330         else
331         {
332             return factory.getInstance(loader, params, signature);
333         }
334     }
335 
336     /***
337      * Tests if specified class loaders are supported for a named class.
338      *
339      * @param className the name of the class.
340      * @return true if class loaders are supported, false otherwise.
341      * @throws TurbineException if test fails.
342      */
343     public boolean isLoaderSupported(String className)
344             throws TurbineException
345     {
346         Factory factory = getFactory(className);
347         return factory != null ?
348                 factory.isLoaderSupported() : true;
349     }
350 
351     /***
352      * Gets an instance of a specified class.
353      *
354      * @param clazz the class.
355      * @return the instance.
356      * @throws TurbineException if instantiation fails.
357      */
358     protected Object getInstance(Class clazz)
359             throws TurbineException
360     {
361         try
362         {
363             return clazz.newInstance();
364         }
365         catch (Exception x)
366         {
367             throw new TurbineException(
368                     "Instantiation failed for " + clazz.getName(), x);
369         }
370     }
371 
372     /***
373      * Gets an instance of a specified class.
374      * Parameters for its constructor are given as an array of objects,
375      * primitive types must be wrapped with a corresponding class.
376      *
377      * @param clazz the class.
378      * @param params an array containing the parameters of the constructor.
379      * @param signature an array containing the signature of the constructor.
380      * @return the instance.
381      * @throws TurbineException if instantiation fails.
382      */
383     protected Object getInstance(Class clazz,
384             Object params[],
385             String signature[])
386             throws TurbineException
387     {
388         /* Try to construct. */
389         try
390         {
391             Class[] sign = getSignature(clazz, params, signature);
392             return clazz.getConstructor(sign).newInstance(params);
393         }
394         catch (Exception x)
395         {
396             throw new TurbineException(
397                     "Instantiation failed for " + clazz.getName(), x);
398         }
399     }
400 
401     /***
402      * Gets the signature classes for parameters of a method of a class.
403      *
404      * @param clazz the class.
405      * @param params an array containing the parameters of the method.
406      * @param signature an array containing the signature of the method.
407      * @return an array of signature classes. Note that in some cases
408      * objects in the parameter array can be switched to the context
409      * of a different class loader.
410      * @throws ClassNotFoundException if any of the classes is not found.
411      */
412     public Class[] getSignature(Class clazz,
413             Object params[],
414             String signature[])
415             throws ClassNotFoundException
416     {
417         if (signature != null)
418         {
419             /* We have parameters. */
420             ClassLoader tempLoader;
421             ClassLoader loader = clazz.getClassLoader();
422             Class[] sign = new Class[signature.length];
423             for (int i = 0; i < signature.length; i++)
424             {
425                 /* Check primitive types. */
426                 sign[i] = getPrimitiveClass(signature[i]);
427                 if (sign[i] == null)
428                 {
429                     /* Not a primitive one, continue building. */
430                     if (loader != null)
431                     {
432                         /* Use the class loader of the target object. */
433                         sign[i] = loader.loadClass(signature[i]);
434                         tempLoader = sign[i].getClassLoader();
435                         if ((params[i] != null) &&
436                                 (tempLoader != null) &&
437                                 !tempLoader.equals(params[i].getClass().getClassLoader()))
438                         {
439                             /*
440                              * The class uses a different class loader,
441                              * switch the parameter.
442                              */
443                             params[i] = switchObjectContext(params[i], loader);
444                         }
445                     }
446                     else
447                     {
448                         /* Use the default class loader. */
449                         sign[i] = loadClass(signature[i]);
450                     }
451                 }
452             }
453             return sign;
454         }
455         else
456         {
457             return null;
458         }
459     }
460 
461     /***
462      * Switches an object into the context of a different class loader.
463      *
464      * @param object an object to switch.
465      * @param loader the loader of the new context.
466      */
467     protected Object switchObjectContext(Object object,
468             ClassLoader loader)
469     {
470         ByteArrayOutputStream bout =
471                 new ByteArrayOutputStream();
472         try
473         {
474             ObjectOutputStream out =
475                     new ObjectOutputStream(bout);
476             out.writeObject(object);
477             out.flush();
478         }
479         catch (Exception x)
480         {
481             return object;
482         }
483 
484         try
485         {
486             ByteArrayInputStream bin =
487                     new ByteArrayInputStream(bout.toByteArray());
488             ObjectInputStreamForContext in =
489                     new ObjectInputStreamForContext(bin, loader);
490 
491             return in.readObject();
492         }
493         catch (Exception x)
494         {
495             return object;
496         }
497     }
498 
499     /***
500      * Loads the named class using the default class loader.
501      *
502      * @param className the name of the class to load.
503      * @return the loaded class.
504      * @throws ClassNotFoundException if the class was not found.
505      */
506     protected Class loadClass(String className)
507             throws ClassNotFoundException
508     {
509         ClassLoader loader = this.getClass().getClassLoader();
510         try
511         {
512             return loader != null ?
513                     loader.loadClass(className) : Class.forName(className);
514         }
515         catch (ClassNotFoundException x)
516         {
517             /* Go through additional loaders. */
518             for (Iterator i = classLoaders.iterator(); i.hasNext();)
519             {
520                 try
521                 {
522                     return ((ClassLoader) i.next()).loadClass(className);
523                 }
524                 catch (ClassNotFoundException xx)
525                 {
526                 }
527             }
528 
529             /* Give up. */
530             throw x;
531         }
532     }
533 
534     /***
535      * Loads the named class using a specified class loader.
536      *
537      * @param className the name of the class to load.
538      * @param loader the loader to use.
539      * @return the loaded class.
540      * @throws ClassNotFoundException if the class was not found.
541      */
542     protected Class loadClass(String className,
543             ClassLoader loader)
544             throws ClassNotFoundException
545     {
546         return loader != null ?
547                 loader.loadClass(className) : loadClass(className);
548     }
549 
550     /***
551      * Gets a customized factory for a named class.
552      *
553      * @param className the name of the class to load.
554      * @return the factory or null if not specified.
555      * @throws TurbineException if instantiation of the factory fails.
556      */
557     protected Factory getFactory(String className)
558             throws TurbineException
559     {
560         HashMap factories = objectFactories;
561         Object factory = factories.get(className);
562         if (factory != null)
563         {
564             if (factory instanceof String)
565             {
566                 /* Not yet instantiated... */
567                 try
568                 {
569                     factory = (Factory) getInstance((String) factory);
570                     ((Factory) factory).init(className);
571                 }
572                 catch (TurbineException x)
573                 {
574                     throw x;
575                 }
576                 catch (ClassCastException x)
577                 {
578                     throw new TurbineException(
579                             "Incorrect factory " + (String) factory +
580                             " for class " + className, x);
581                 }
582                 factories = (HashMap) factories.clone();
583                 factories.put(className, factory);
584                 objectFactories = factories;
585             }
586             return (Factory) factory;
587         }
588         else
589         {
590             return null;
591         }
592     }
593 }