View Javadoc

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