001 package org.apache.fulcrum.factory;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.io.ByteArrayInputStream;
023 import java.io.ByteArrayOutputStream;
024 import java.io.ObjectOutputStream;
025 import java.util.ArrayList;
026 import java.util.HashMap;
027 import java.util.Iterator;
028
029 import org.apache.avalon.framework.activity.Initializable;
030 import org.apache.avalon.framework.configuration.Configurable;
031 import org.apache.avalon.framework.configuration.Configuration;
032 import org.apache.avalon.framework.configuration.ConfigurationException;
033 import org.apache.avalon.framework.logger.AbstractLogEnabled;
034 import org.apache.fulcrum.factory.utils.ObjectInputStreamForContext;
035
036 /**
037 * The Factory Service instantiates objects using specified
038 * class loaders. If none is specified, the default one
039 * will be used.
040 *
041 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
042 * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
043 * @author <a href="mailto:mcconnell@apache.org">Stephen McConnell</a>
044 * @version $Id: DefaultFactoryService.java 670327 2008-06-22 09:32:41Z tv $
045 *
046 * @avalon.component name="factory" lifestyle="singleton"
047 * @avalon.service type="org.apache.fulcrum.factory.FactoryService"
048 */
049 public class DefaultFactoryService
050 extends AbstractLogEnabled
051 implements FactoryService, Configurable, Initializable
052 {
053 protected boolean initialized = false;
054 //private boolean disposed = false;
055 /**
056 * The property specifying a set of additional class loaders.
057 */
058 private static final String CLASS_LOADER = "classloader";
059 /**
060 * The property prefix specifying additional object factories.
061 */
062 private static final String OBJECT_FACTORY = "object-factory";
063 /**
064 * The name of the default factory.
065 */
066 protected static final String DEFAULT_FACTORY = "default";
067 /**
068 * Primitive classes for reflection of constructors.
069 */
070 private static HashMap primitiveClasses;
071 {
072 primitiveClasses = new HashMap(8);
073 primitiveClasses.put(Boolean.TYPE.toString(), Boolean.TYPE);
074 primitiveClasses.put(Character.TYPE.toString(), Character.TYPE);
075 primitiveClasses.put(Byte.TYPE.toString(), Byte.TYPE);
076 primitiveClasses.put(Short.TYPE.toString(), Short.TYPE);
077 primitiveClasses.put(Integer.TYPE.toString(), Integer.TYPE);
078 primitiveClasses.put(Long.TYPE.toString(), Long.TYPE);
079 primitiveClasses.put(Float.TYPE.toString(), Float.TYPE);
080 primitiveClasses.put(Double.TYPE.toString(), Double.TYPE);
081 }
082 /**
083 * temporary storage of class names between configure and initialize
084 */
085 private String[] loaderNames;
086 /**
087 * Additional class loaders.
088 */
089 private ArrayList classLoaders = new ArrayList();
090 /**
091 * Customized object factories.
092 */
093 private HashMap objectFactories = new HashMap();
094 /**
095 * Gets the class of a primitive type.
096 *
097 * @param type a primitive type.
098 * @return the corresponding class, or null.
099 */
100 protected static Class getPrimitiveClass(String type)
101 {
102 return (Class) primitiveClasses.get(type);
103 }
104
105 /**
106 * Gets an instance of a named class.
107 *
108 * @param className the name of the class.
109 * @return the instance.
110 * @throws FactoryException if instantiation fails.
111 */
112 public Object getInstance(String className) throws FactoryException
113 {
114 if (className == null)
115 {
116 throw new FactoryException("Missing String className");
117 }
118 Factory factory = getFactory(className);
119 if (factory == null)
120 {
121 Class clazz;
122 try
123 {
124 clazz = loadClass(className);
125 }
126 catch (ClassNotFoundException x)
127 {
128 throw new FactoryException("Instantiation failed for class " + className, x);
129 }
130 return getInstance(clazz);
131 }
132 else
133 {
134 return factory.getInstance();
135 }
136 }
137 /**
138 * Gets an instance of a named class using a specified class loader.
139 *
140 * <p>Class loaders are supported only if the isLoaderSupported
141 * method returns true. Otherwise the loader parameter is ignored.
142 *
143 * @param className the name of the class.
144 * @param loader the class loader.
145 * @return the instance.
146 * @throws FactoryException if instantiation fails.
147 */
148 public Object getInstance(String className, ClassLoader loader) throws FactoryException
149 {
150 Factory factory = getFactory(className);
151 if (factory == null)
152 {
153 if (loader != null)
154 {
155 Class clazz;
156 try
157 {
158 clazz = loadClass(className, loader);
159 }
160 catch (ClassNotFoundException x)
161 {
162 throw new FactoryException("Instantiation failed for class " + className, x);
163 }
164 return getInstance(clazz);
165 }
166 else
167 {
168 return getInstance(className);
169 }
170 }
171 else
172 {
173 return factory.getInstance(loader);
174 }
175 }
176 /**
177 * Gets an instance of a named class.
178 * Parameters for its constructor are given as an array of objects,
179 * primitive types must be wrapped with a corresponding class.
180 *
181 * @param className the name of the class.
182 * @param params an array containing the parameters of the constructor.
183 * @param signature an array containing the signature of the constructor.
184 * @return the instance.
185 * @throws FactoryException if instantiation fails.
186 */
187 public Object getInstance(String className, Object[] params, String[] signature) throws FactoryException
188 {
189 Factory factory = getFactory(className);
190 if (factory == null)
191 {
192 Class clazz;
193 try
194 {
195 clazz = loadClass(className);
196 }
197 catch (ClassNotFoundException x)
198 {
199 throw new FactoryException("Instantiation failed for class " + className, x);
200 }
201 return getInstance(clazz, params, signature);
202 }
203 else
204 {
205 return factory.getInstance(params, signature);
206 }
207 }
208 /**
209 * Gets an instance of a named class using a specified class loader.
210 * Parameters for its constructor are given as an array of objects,
211 * primitive types must be wrapped with a corresponding class.
212 *
213 * <p>Class loaders are supported only if the isLoaderSupported
214 * method returns true. Otherwise the loader parameter is ignored.
215 *
216 * @param className the name of the class.
217 * @param loader the class loader.
218 * @param params an array containing the parameters of the constructor.
219 * @param signature an array containing the signature of the constructor.
220 * @return the instance.
221 * @throws FactoryException if instantiation fails.
222 */
223 public Object getInstance(String className, ClassLoader loader, Object[] params, String[] signature)
224 throws FactoryException
225 {
226 Factory factory = getFactory(className);
227 if (factory == null)
228 {
229 if (loader != null)
230 {
231 Class clazz;
232 try
233 {
234 clazz = loadClass(className, loader);
235 }
236 catch (ClassNotFoundException x)
237 {
238 throw new FactoryException("Instantiation failed for class " + className, x);
239 }
240 return getInstance(clazz, params, signature);
241 }
242 else
243 {
244 return getInstance(className, params, signature);
245 }
246 }
247 else
248 {
249 return factory.getInstance(loader, params, signature);
250 }
251 }
252 /**
253 * Tests if specified class loaders are supported for a named class.
254 *
255 * @param className the name of the class.
256 * @return true if class loaders are supported, false otherwise.
257 * @throws FactoryException if test fails.
258 */
259 public boolean isLoaderSupported(String className) throws FactoryException
260 {
261 Factory factory = getFactory(className);
262 return factory != null ? factory.isLoaderSupported() : true;
263 }
264 /**
265 * Gets an instance of a specified class.
266 *
267 * @param clazz the class.
268 * @return the instance.
269 * @throws FactoryException if instantiation fails.
270 */
271 public Object getInstance(Class clazz) throws FactoryException
272 {
273 try
274 {
275 return clazz.newInstance();
276 }
277 catch (Exception x)
278 {
279 throw new FactoryException("Instantiation failed for " + clazz.getName(), x);
280 }
281 }
282 /**
283 * Gets an instance of a specified class.
284 * Parameters for its constructor are given as an array of objects,
285 * primitive types must be wrapped with a corresponding class.
286 *
287 * @param clazz the class.
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 FactoryException if instantiation fails.
292 */
293 protected Object getInstance(Class clazz, Object params[], String signature[]) throws FactoryException
294 {
295 /* Try to construct. */
296 try
297 {
298 Class[] sign = getSignature(clazz, params, signature);
299 return clazz.getConstructor(sign).newInstance(params);
300 }
301 catch (Exception x)
302 {
303 throw new FactoryException("Instantiation failed for " + clazz.getName(), x);
304 }
305 }
306 /**
307 * Gets the signature classes for parameters of a method of a class.
308 *
309 * @param clazz the class.
310 * @param params an array containing the parameters of the method.
311 * @param signature an array containing the signature of the method.
312 * @return an array of signature classes. Note that in some cases
313 * objects in the parameter array can be switched to the context
314 * of a different class loader.
315 * @throws ClassNotFoundException if any of the classes is not found.
316 */
317 public Class[] getSignature(Class clazz, Object params[], String signature[]) throws ClassNotFoundException
318 {
319 if (signature != null)
320 {
321 /* We have parameters. */
322 ClassLoader tempLoader;
323 ClassLoader loader = clazz.getClassLoader();
324 Class[] sign = new Class[signature.length];
325 for (int i = 0; i < signature.length; i++)
326 {
327 /* Check primitive types. */
328 sign[i] = getPrimitiveClass(signature[i]);
329 if (sign[i] == null)
330 {
331 /* Not a primitive one, continue building. */
332 if (loader != null)
333 {
334 /* Use the class loader of the target object. */
335 sign[i] = loader.loadClass(signature[i]);
336 tempLoader = sign[i].getClassLoader();
337 if ((params[i] != null)
338 && (tempLoader != null)
339 && !tempLoader.equals(params[i].getClass().getClassLoader()))
340 {
341 /*
342 * The class uses a different class loader,
343 * switch the parameter.
344 */
345 params[i] = switchObjectContext(params[i], loader);
346 }
347 }
348 else
349 {
350 /* Use the default class loader. */
351 sign[i] = loadClass(signature[i]);
352 }
353 }
354 }
355 return sign;
356 }
357 else
358 {
359 return null;
360 }
361 }
362 /**
363 * Switches an object into the context of a different class loader.
364 *
365 * @param object an object to switch.
366 * @param loader the loader of the new context.
367 */
368 protected Object switchObjectContext(Object object, ClassLoader loader)
369 {
370 ByteArrayOutputStream bout = new ByteArrayOutputStream();
371 try
372 {
373 ObjectOutputStream out = new ObjectOutputStream(bout);
374 out.writeObject(object);
375 out.flush();
376 }
377 catch (Exception x)
378 {
379 return object;
380 }
381 try
382 {
383 ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
384 ObjectInputStreamForContext in = new ObjectInputStreamForContext(bin, loader);
385 return in.readObject();
386 }
387 catch (Exception x)
388 {
389 return object;
390 }
391 }
392 /**
393 * Loads the named class using the default class loader.
394 *
395 * @param className the name of the class to load.
396 * @return the loaded class.
397 * @throws ClassNotFoundException if the class was not found.
398 */
399 protected Class loadClass(String className) throws ClassNotFoundException
400 {
401 ClassLoader loader = this.getClass().getClassLoader();
402 try
403 {
404 return loader != null ? loader.loadClass(className) : Class.forName(className);
405 }
406 catch (ClassNotFoundException x)
407 {
408 /* Go through additional loaders. */
409 for (Iterator i = classLoaders.iterator(); i.hasNext();)
410 {
411 try
412 {
413 return ((ClassLoader) i.next()).loadClass(className);
414 }
415 catch (ClassNotFoundException xx)
416 {
417 // continue
418 }
419 }
420 /* Give up. */
421 throw x;
422 }
423 }
424 /**
425 * Loads the named class using a specified class loader.
426 *
427 * @param className the name of the class to load.
428 * @param loader the loader to use.
429 * @return the loaded class.
430 * @throws ClassNotFoundException if the class was not found.
431 */
432 protected Class loadClass(String className, ClassLoader loader) throws ClassNotFoundException
433 {
434 return loader != null ? loader.loadClass(className) : loadClass(className);
435 }
436 /**
437 * Gets a customized factory for a named class. If no class-specific
438 * factory is specified but a default factory is, will use the default
439 * factory.
440 *
441 * @param className the name of the class to load.
442 * @return the factory, or null if not specified and no default.
443 * @throws FactoryException if instantiation of the factory fails.
444 */
445 protected Factory getFactory(String className) throws FactoryException
446 {
447 HashMap factories = objectFactories;
448 Object factory = factories.get(className);
449 if (factory == null)
450 {
451 //No named factory for this; try the default, if one
452 //exists.
453 factory = factories.get(DEFAULT_FACTORY);
454 }
455 if (factory != null)
456 {
457 if (factory instanceof String)
458 {
459 /* Not yet instantiated... */
460 try
461 {
462 factory = (Factory) getInstance((String) factory);
463 ((Factory) factory).init(className);
464 }
465 catch (FactoryException x)
466 {
467 throw x;
468 }
469 catch (ClassCastException x)
470 {
471 throw new FactoryException("Incorrect factory " + (String) factory + " for class " + className, x);
472 }
473 factories = (HashMap) factories.clone();
474 factories.put(className, factory);
475 objectFactories = factories;
476 }
477 return (Factory) factory;
478 }
479 else
480 {
481 return null;
482 }
483 }
484 // ---------------- Avalon Lifecycle Methods ---------------------
485 /**
486 * Avalon component lifecycle method
487 */
488 public void configure(Configuration conf) throws ConfigurationException
489 {
490 final Configuration[] loaders = conf.getChildren(CLASS_LOADER);
491 if (loaders != null)
492 {
493 loaderNames = new String[loaders.length];
494 for (int i = 0; i < loaders.length; i++)
495 {
496 loaderNames[i] = loaders[i].getValue();
497 }
498 }
499 final Configuration factories = conf.getChild(OBJECT_FACTORY, false);
500 if (factories != null)
501 {
502 Configuration[] nameVal = factories.getChildren();
503 for (int i = 0; i < nameVal.length; i++)
504 {
505 String key = nameVal[i].getName();
506 String factory = nameVal[i].getValue();
507 // Store the factory to the table as a string and
508 // instantiate it by using the service when needed.
509 objectFactories.put(key, factory);
510 }
511 }
512 }
513 /**
514 * Avalon component lifecycle method
515 * Initializes the service by loading default class loaders
516 * and customized object factories.
517 *
518 * @throws InitializationException if initialization fails.
519 */
520 public void initialize() throws Exception
521 {
522 if (loaderNames != null)
523 {
524 for (int i = 0; i < loaderNames.length; i++)
525 {
526 try
527 {
528 classLoaders.add(loadClass(loaderNames[i]).newInstance());
529 }
530 catch (Exception x)
531 {
532 throw new Exception(
533 "No such class loader '" + loaderNames[i] + "' for DefaultFactoryService",
534 x);
535 }
536 }
537 loaderNames = null;
538 }
539 }
540 }