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