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    }