001    package org.apache.fulcrum.pool;
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.lang.reflect.Method;
023    import java.util.ArrayList;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.Map;
027    import org.apache.avalon.framework.activity.Disposable;
028    import org.apache.avalon.framework.activity.Initializable;
029    import org.apache.avalon.framework.service.ServiceManager;
030    import org.apache.avalon.framework.service.Serviceable;
031    import org.apache.avalon.framework.configuration.Configurable;
032    import org.apache.avalon.framework.configuration.Configuration;
033    import org.apache.avalon.framework.logger.AbstractLogEnabled;
034    import org.apache.fulcrum.factory.FactoryException;
035    import org.apache.fulcrum.factory.FactoryService;
036    
037    /**
038     * The Pool Service extends the Factory Service by adding support
039     * for pooling instantiated objects. When a new instance is
040     * requested, the service first checks its pool if one is available.
041     * If the the pool is empty, a new instance will be requested
042     * from the FactoryService.
043     *
044     * For objects implementing the Recyclable interface, a recycle
045     * method will be called, when they taken from the pool, and
046     * a dispose method, when they are returned to the pool.
047     *
048     * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
049     * @author <a href="mailto:mcconnell@apache.org">Stephen McConnell</a>
050     * @version $Id: DefaultPoolService.java 670330 2008-06-22 09:37:21Z tv $
051     *
052     * @avalon.component name="pool" lifestyle="transient"
053     * @avalon.service type="org.apache.fulcrum.pool.PoolService"
054     */
055    public class DefaultPoolService extends AbstractLogEnabled implements PoolService, Serviceable, Disposable, Initializable, Configurable
056    {
057        /**
058         * The property specifying the pool capacity.
059         */
060        public static final String POOL_CAPACITY = "capacity";
061        /**
062         * An inner class for class specific pools.
063         */
064        private class PoolBuffer
065        {
066            /**
067             * An inner class for cached recycle methods.
068             */
069            private class Recycler
070            {
071                /**
072                 * The method.
073                 */
074                private final Method recycle;
075                /**
076                 * The signature.
077                 */
078                private final String[] signature;
079                /**
080                 * Constructs a new recycler.
081                 *
082                 * @param rec the recycle method.
083                 * @param sign the signature.
084                 */
085                public Recycler(Method rec, String[] sign)
086                {
087                    recycle = rec;
088                    signature = (sign != null) && (sign.length > 0) ? sign : null;
089                }
090                /**
091                 * Matches the given signature against
092                 * that of the recycle method of this recycler.
093                 *
094                 * @param sign the signature.
095                 * @return the matching recycle method or null.
096                 */
097                public Method match(String[] sign)
098                {
099                    if ((sign != null) && (sign.length > 0))
100                    {
101                        if ((signature != null) && (sign.length == signature.length))
102                        {
103                            for (int i = 0; i < signature.length; i++)
104                            {
105                                if (!signature[i].equals(sign[i]))
106                                {
107                                    return null;
108                                }
109                            }
110                            return recycle;
111                        }
112                        else
113                        {
114                            return null;
115                        }
116                    }
117                    else if (signature == null)
118                    {
119                        return recycle;
120                    }
121                    else
122                    {
123                        return null;
124                    }
125                }
126            }
127            /**
128             * A buffer for class instances.
129             */
130            private BoundedBuffer pool;
131            /**
132             * A flag to determine if a more efficient recycler is implemented.
133             */
134            private boolean arrayCtorRecyclable;
135            /**
136             * A cache for recycling methods.
137             */
138            private ArrayList recyclers;
139            /**
140             * Contructs a new pool buffer with a specific capacity.
141             *
142             * @param capacity a capacity.
143             */
144            public PoolBuffer(int capacity)
145            {
146                pool = new BoundedBuffer(capacity);
147            }
148            /**
149             * Tells pool that it contains objects which can be
150             * initialized using an Object array.
151             *
152             * @param isArrayCtor a <code>boolean</code> value
153             */
154            public void setArrayCtorRecyclable(boolean isArrayCtor)
155            {
156                arrayCtorRecyclable = isArrayCtor;
157            }
158            /**
159             * Polls for an instance from the pool.
160             *
161             * @return an instance or null.
162             */
163            public Object poll(Object[] params, String[] signature) throws PoolException
164            {
165                Object instance = pool.poll();
166                if (instance != null)
167                {
168                    if (arrayCtorRecyclable)
169                    {
170                        ((ArrayCtorRecyclable) instance).recycle(params);
171                    }
172                    else if (instance instanceof Recyclable)
173                    {
174                        try
175                        {
176                            if ((signature != null) && (signature.length > 0))
177                            {
178                                /* Get the recycle method from the cache. */
179                                Method recycle = getRecycle(signature);
180                                if (recycle == null)
181                                {
182                                    synchronized (this)
183                                    {
184                                        /* Make a synchronized recheck. */
185                                        recycle = getRecycle(signature);
186                                        if (recycle == null)
187                                        {
188                                            Class clazz = instance.getClass();
189                                            recycle =
190                                                clazz.getMethod(
191                                                    "recycle",
192                                                    getFactory().getSignature(clazz, params, signature));
193                                            ArrayList cache =
194                                                recyclers != null ? (ArrayList) recyclers.clone() : new ArrayList();
195                                            cache.add(new Recycler(recycle, signature));
196                                            recyclers = cache;
197                                        }
198                                    }
199                                }
200                                recycle.invoke(instance, params);
201                            }
202                            else
203                            {
204                                ((Recyclable) instance).recycle();
205                            }
206                        }
207                        catch (Exception x)
208                        {
209                            throw new PoolException("Recycling failed for " + instance.getClass().getName(), x);
210                        }
211                    }
212                }
213                return instance;
214            }
215            /**
216             * Offers an instance to the pool.
217             *
218             * @param instance an instance.
219             */
220            public boolean offer(Object instance)
221            {
222                if (instance instanceof Recyclable)
223                {
224                    try
225                    {
226                        ((Recyclable) instance).dispose();
227                    }
228                    catch (Exception x)
229                    {
230                        return false;
231                    }
232                }
233                return pool.offer(instance);
234            }
235            /**
236             * Returns the capacity of the pool.
237             *
238             * @return the capacity.
239             */
240            public int capacity()
241            {
242                return pool.capacity();
243            }
244            /**
245             * Returns the size of the pool.
246             *
247             * @return the size.
248             */
249            public int size()
250            {
251                return pool.size();
252            }
253            /**
254             * Returns a cached recycle method
255             * corresponding to the given signature.
256             *
257             * @param signature the signature.
258             * @return the recycle method or null.
259             */
260            private Method getRecycle(String[] signature)
261            {
262                ArrayList cache = recyclers;
263                if (cache != null)
264                {
265                    Method recycle;
266                    for (Iterator i = cache.iterator(); i.hasNext();)
267                    {
268                        recycle = ((Recycler) i.next()).match(signature);
269                        if (recycle != null)
270                        {
271                            return recycle;
272                        }
273                    }
274                }
275                return null;
276            }
277        }
278        /**
279         * The default capacity of pools.
280         */
281        private int poolCapacity = DEFAULT_POOL_CAPACITY;
282        /**
283         * The pool repository, one pool for each class.
284         */
285        private HashMap poolRepository = new HashMap();
286        private Map capacityMap;
287        private FactoryService factoryService;
288        private ServiceManager manager;
289    
290        /**
291         * Gets an instance of a named class either from the pool
292         * or by calling the Factory Service if the pool is empty.
293         *
294         * @param className the name of the class.
295         * @return the instance.
296         * @throws PoolException if recycling fails.
297         */
298        public Object getInstance(String className) throws PoolException
299        {
300            try
301            {
302                Object instance = pollInstance(className, null, null);
303                return instance == null ? getFactory().getInstance(className) : instance;
304            }
305            catch (FactoryException fe)
306            {
307                throw new PoolException(fe);
308            }
309        }
310        /**
311         * Gets an instance of a named class either from the pool
312         * or by calling the Factory Service if the pool is empty.
313         * The specified class loader will be passed to the Factory Service.
314         *
315         * @param className the name of the class.
316         * @param loader the class loader.
317         * @return the instance.
318         * @throws PoolException if recycling fails.
319         */
320        public Object getInstance(String className, ClassLoader loader) throws PoolException
321        {
322            try
323            {
324                Object instance = pollInstance(className, null, null);
325                return instance == null ? getFactory().getInstance(className, loader) : instance;
326            }
327            catch (FactoryException fe)
328            {
329                throw new PoolException(fe);
330            }
331        }
332        /**
333         * Gets an instance of a named class either from the pool
334         * or by calling the Factory Service if the pool is empty.
335         * Parameters for its constructor are given as an array of objects,
336         * primitive types must be wrapped with a corresponding class.
337         *
338         * @param className the name of the class.
339         * @param loader the class loader.
340         * @param params an array containing the parameters of the constructor.
341         * @param signature an array containing the signature of the constructor.
342         * @return the instance.
343         * @throws PoolException if recycling fails.
344         */
345        public Object getInstance(String className, Object[] params, String[] signature) throws PoolException
346        {
347            try
348            {
349                Object instance = pollInstance(className, params, signature);
350                return instance == null ? getFactory().getInstance(className, params, signature) : instance;
351            }
352            catch (FactoryException fe)
353            {
354                throw new PoolException(fe);
355            }
356        }
357        /**
358         * Gets an instance of a named class either from the pool
359         * or by calling the Factory Service if the pool is empty.
360         * Parameters for its constructor are given as an array of objects,
361         * primitive types must be wrapped with a corresponding class.
362         * The specified class loader will be passed to the Factory Service.
363         *
364         * @param className the name of the class.
365         * @param loader the class loader.
366         * @param params an array containing the parameters of the constructor.
367         * @param signature an array containing the signature of the constructor.
368         * @return the instance.
369         * @throws PoolException if recycling fails.
370         */
371        public Object getInstance(String className, ClassLoader loader, Object[] params, String[] signature)
372            throws PoolException
373        {
374            try
375            {
376                Object instance = pollInstance(className, params, signature);
377                return instance == null ? getFactory().getInstance(className, loader, params, signature) : instance;
378            }
379            catch (FactoryException fe)
380            {
381                throw new PoolException(fe);
382            }
383        }
384        /**
385         * Tests if specified class loaders are supported for a named class.
386         *
387         * @param className the name of the class.
388         * @return true if class loaders are supported, false otherwise.
389         * @throws PoolException if test fails.
390         */
391        public boolean isLoaderSupported(String className) throws FactoryException
392        {
393            return getFactory().isLoaderSupported(className);
394        }
395        /**
396         * Gets an instance of a specified class either from the pool
397         * or by instatiating from the class if the pool is empty.
398         *
399         * @param clazz the class.
400         * @return the instance.
401         * @throws PoolException if recycling fails.
402         */
403        public Object getInstance(Class clazz) throws PoolException
404        {
405            try
406            {
407                Object instance = pollInstance(clazz.getName(), null, null);
408                return instance == null ? factoryService.getInstance(clazz) : instance;
409            }
410            catch (FactoryException fe)
411            {
412                throw new PoolException(fe);
413            }
414        }
415        /**
416         * Gets an instance of a specified class either from the pool
417         * or by instatiating from the class if the pool is empty.
418         *
419         * @todo There is a whacky .toString() on the clazzz, but otherwise it
420         * won't compile..
421         * @param clazz the class.
422         * @param params an array containing the parameters of the constructor.
423         * @param signature an array containing the signature of the constructor.
424         * @return the instance.
425         * @throws PoolException if recycling fails.
426         */
427        public Object getInstance(Class clazz, Object params[], String signature[]) throws PoolException
428        {
429            try
430            {
431                Object instance = pollInstance(clazz.getName(), params, signature);
432                //FactoryService fs = getFactory();
433                return instance == null ? getFactory().getInstance(clazz.toString(), params, signature) : instance;
434            }
435            catch (FactoryException fe)
436            {
437                throw new PoolException(fe);
438            }
439        }
440        /**
441         * Puts a used object back to the pool. Objects implementing
442         * the Recyclable interface can provide a recycle method to
443         * be called when they are reused and a dispose method to be
444         * called when they are returned to the pool.
445         *
446         * @param instance the object instance to recycle.
447         * @return true if the instance was accepted.
448         */
449        public boolean putInstance(Object instance)
450        {
451            if (instance != null)
452            {
453                HashMap repository = poolRepository;
454                String className = instance.getClass().getName();
455                PoolBuffer pool = (PoolBuffer) repository.get(className);
456                if (pool == null)
457                {
458                    pool = new PoolBuffer(getCapacity(className));
459                    repository = (HashMap) repository.clone();
460                    repository.put(className, pool);
461                    poolRepository = repository;
462                    if (instance instanceof ArrayCtorRecyclable)
463                    {
464                        pool.setArrayCtorRecyclable(true);
465                    }
466                }
467                return pool.offer(instance);
468            }
469            else
470            {
471                return false;
472            }
473        }
474        /**
475         * Gets the capacity of the pool for a named class.
476         *
477         * @param className the name of the class.
478         */
479        public int getCapacity(String className)
480        {
481            PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
482            if (pool == null)
483            {
484                /* Check class specific capacity. */
485                int capacity = poolCapacity;
486                if (capacityMap != null)
487                {
488                    Integer cap = (Integer) capacityMap.get(className);
489                    if (cap != null)
490                    {
491                        capacity = cap.intValue();
492                    }
493                }
494                return capacity;
495            }
496            else
497            {
498                return pool.capacity();
499            }
500        }
501        /**
502         * Sets the capacity of the pool for a named class.
503         * Note that the pool will be cleared after the change.
504         *
505         * @param className the name of the class.
506         * @param capacity the new capacity.
507         */
508        public void setCapacity(String className, int capacity)
509        {
510            HashMap repository = poolRepository;
511            repository = repository != null ? (HashMap) repository.clone() : new HashMap();
512            repository.put(className, new PoolBuffer(capacity));
513            poolRepository = repository;
514        }
515        /**
516         * Gets the current size of the pool for a named class.
517         *
518         * @param className the name of the class.
519         */
520        public int getSize(String className)
521        {
522            PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
523            return pool != null ? pool.size() : 0;
524        }
525        /**
526         * Clears instances of a named class from the pool.
527         *
528         * @param className the name of the class.
529         */
530        public void clearPool(String className)
531        {
532            HashMap repository = poolRepository;
533            if (repository.get(className) != null)
534            {
535                repository = (HashMap) repository.clone();
536                repository.remove(className);
537                poolRepository = repository;
538            }
539        }
540        /**
541         * Clears all instances from the pool.
542         */
543        public void clearPool()
544        {
545            poolRepository = new HashMap();
546        }
547        /**
548         * Polls and recycles an object of the named class from the pool.
549         *
550         * @param className the name of the class.
551         * @param params an array containing the parameters of the constructor.
552         * @param signature an array containing the signature of the constructor.
553         * @return the object or null.
554         * @throws PoolException if recycling fails.
555         */
556        private Object pollInstance(String className, Object[] params, String[] signature) throws PoolException
557        {
558            PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
559            return pool != null ? pool.poll(params, signature) : null;
560        }
561        /**
562         * Gets the factory service.
563         *
564         * @return the factory service.
565         */
566        protected FactoryService getFactory()
567        {
568            return factoryService;
569        }
570        
571        // ---------------- Avalon Lifecycle Methods ---------------------
572        /**
573         * Avalon component lifecycle method
574         */
575        public void configure(Configuration conf)
576        {
577            final Configuration capacities = conf.getChild(POOL_CAPACITY, false);
578            if (capacities != null)
579            {
580                Configuration defaultConf = capacities.getChild("default");
581                int capacity = defaultConf.getValueAsInteger(DEFAULT_POOL_CAPACITY);
582                if (capacity <= 0)
583                {
584                    throw new IllegalArgumentException("Capacity must be >0");
585                }
586                poolCapacity = capacity;
587                Configuration[] nameVal = capacities.getChildren();
588                for (int i = 0; i < nameVal.length; i++)
589                {
590                    String key = nameVal[i].getName();
591                    if (!"default".equals(key))
592                    {
593                        capacity = nameVal[i].getValueAsInteger(poolCapacity);
594                        if (capacity < 0)
595                        {
596                            capacity = poolCapacity;
597                        }
598                        if (capacityMap == null)
599                        {
600                            capacityMap = new HashMap();
601                        }
602                        capacityMap.put(key, new Integer(capacity));
603                    }
604                }
605            }
606        }
607    
608        /**
609         * Avalon component lifecycle method
610         * @avalon.dependency type="org.apache.fulcrum.factory.FactoryService"
611         */
612        public void service(ServiceManager manager)
613        {
614            this.manager = manager;
615        }
616    
617        /**
618         * Avalon component lifecycle method
619         * Initializes the service by loading default class loaders
620         * and customized object factories.
621         *
622         * @throws InitializationException if initialization fails.
623         */
624        public void initialize() throws Exception
625        {
626            try
627            {
628                factoryService = (FactoryService) manager.lookup(FactoryService.ROLE);
629            }
630            catch (Exception e)
631            {
632                throw new Exception(
633                   "DefaultPoolService.initialize: Failed to get a Factory object", e);
634            }
635        }
636    
637        /**
638         * Avalon component lifecycle method
639         */
640        public void dispose()
641        {
642            if (factoryService != null)
643            {
644                manager.release(factoryService);
645            }
646            factoryService = null;
647            manager = null;
648        }
649    }