View Javadoc

1   package org.apache.turbine.services.pool;
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.lang.reflect.Method;
23  
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  
28  import org.apache.commons.configuration.Configuration;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  
33  import org.apache.turbine.services.InitializationException;
34  import org.apache.turbine.services.TurbineBaseService;
35  import org.apache.turbine.services.factory.FactoryService;
36  import org.apache.turbine.services.factory.TurbineFactory;
37  import org.apache.turbine.util.TurbineException;
38  import org.apache.turbine.util.pool.ArrayCtorRecyclable;
39  import org.apache.turbine.util.pool.BoundedBuffer;
40  import org.apache.turbine.util.pool.Recyclable;
41  
42  /***
43   * The Pool Service extends the Factory Service by adding support
44   * for pooling instantiated objects. When a new instance is
45   * requested, the service first checks its pool if one is available.
46   * If the the pool is empty, a new instance will be requested
47   * from the FactoryService.
48   *
49   * <p>For objects implementing the Recyclable interface, a recycle
50   * method will be called, when they taken from the pool, and
51   * a dispose method, when they are returned to the pool.
52   *
53   * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
54   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
55   * @version $Id: TurbinePoolService.java 534527 2007-05-02 16:10:59Z tv $
56   */
57  public class TurbinePoolService
58          extends TurbineBaseService
59          implements PoolService
60  {
61      /*** Are we currently debugging the pool recycling? */
62      private boolean debugPool = false;
63  
64      /*** Internal Reference to the Factory */
65      private FactoryService factoryService;
66  
67      /*** Logging */
68      private static Log log = LogFactory.getLog(TurbinePoolService.class);
69  
70      /***
71       * An inner class for class specific pools.
72       */
73      private class PoolBuffer
74      {
75          /***
76           * An inner class for cached recycle methods.
77           */
78          private class Recycler
79          {
80              /*** The recycle method. */
81              private final Method recycle;
82  
83              /*** The signature. */
84              private final String[] signature;
85  
86              /***
87               * Constructs a new recycler.
88               *
89               * @param rec the recycle method.
90               * @param sign the signature.
91               */
92              public Recycler(Method rec, String[] sign)
93              {
94                  recycle = rec;
95                  signature = ((sign != null) && (sign.length > 0))
96                          ? sign : null;
97              }
98  
99              /***
100              * Matches the given signature against
101              * that of the recycle method of this recycler.
102              *
103              * @param sign the signature.
104              * @return the matching recycle method or null.
105              */
106             public Method match(String[] sign)
107             {
108                 if ((sign != null) && (sign.length > 0))
109                 {
110                     if ((signature != null)
111                             && (sign.length == signature.length))
112                     {
113                         for (int i = 0; i < signature.length; i++)
114                         {
115                             if (!signature[i].equals(sign[i]))
116                             {
117                                 return null;
118                             }
119                         }
120                         return recycle;
121                     }
122                     else
123                     {
124                         return null;
125                     }
126                 }
127                 else if (signature == null)
128                 {
129                     return recycle;
130                 }
131                 else
132                 {
133                     return null;
134                 }
135             }
136         }
137 
138         /*** A buffer for class instances. */
139         private BoundedBuffer pool;
140 
141         /*** A flag to determine if a more efficient recycler is implemented. */
142         private boolean arrayCtorRecyclable;
143 
144         /*** A cache for recycling methods. */
145         private ArrayList recyclers;
146 
147         /***
148          * Constructs a new pool buffer with a specific capacity.
149          *
150          * @param capacity a capacity.
151          */
152         public PoolBuffer(int capacity)
153         {
154             pool = new BoundedBuffer(capacity);
155         }
156 
157         /***
158          * Tells pool that it contains objects which can be
159          * initialized using an Object array.
160          *
161          * @param isArrayCtor a <code>boolean</code> value
162          */
163         public void setArrayCtorRecyclable(boolean isArrayCtor)
164         {
165             arrayCtorRecyclable = isArrayCtor;
166         }
167 
168         /***
169          * Polls for an instance from the pool.
170          *
171          * @return an instance or null.
172          */
173         public Object poll(Object[] params, String[] signature)
174                 throws TurbineException
175         {
176             // If we're debugging the recycling code, we want different
177             // objects to be used when pulling from the pool. Ensure that
178             // each pool fills up at least to half its capacity.
179             if (debugPool && (pool.size() < (pool.capacity() / 2)))
180             {
181                 log.debug("Size: " + pool.size()
182                         + ", capacity: " + pool.capacity());
183                 return null;
184             }
185 
186             Object instance = pool.poll();
187             if (instance != null)
188             {
189                 if (arrayCtorRecyclable)
190                 {
191                     ((ArrayCtorRecyclable) instance).recycle(params);
192                 }
193                 else if (instance instanceof Recyclable)
194                 {
195                     try
196                     {
197                         if ((signature != null) && (signature.length > 0))
198                         {
199                             /* Get the recycle method from the cache. */
200                             Method recycle = getRecycle(signature);
201                             if (recycle == null)
202                             {
203                                 synchronized (this)
204                                 {
205                                     /* Make a synchronized recheck. */
206                                     recycle = getRecycle(signature);
207                                     if (recycle == null)
208                                     {
209                                         Class clazz = instance.getClass();
210                                         recycle = clazz.getMethod("recycle",
211                                                 factoryService.getSignature(
212                                                         clazz, params, signature));
213                                         ArrayList cache = recyclers != null
214                                                 ? (ArrayList) recyclers.clone()
215                                                 : new ArrayList();
216                                         cache.add(
217                                                 new Recycler(recycle, signature));
218                                         recyclers = cache;
219                                     }
220                                 }
221                             }
222                             recycle.invoke(instance, params);
223                         }
224                         else
225                         {
226                             ((Recyclable) instance).recycle();
227                         }
228                     }
229                     catch (Exception x)
230                     {
231                         throw new TurbineException(
232                                 "Recycling failed for " + instance.getClass().getName(), x);
233                     }
234                 }
235             }
236             return instance;
237         }
238 
239         /***
240          * Offers an instance to the pool.
241          *
242          * @param instance an instance.
243          */
244         public boolean offer(Object instance)
245         {
246             if (instance instanceof Recyclable)
247             {
248                 try
249                 {
250                     ((Recyclable) instance).dispose();
251                 }
252                 catch (Exception x)
253                 {
254                     return false;
255                 }
256             }
257             return pool.offer(instance);
258         }
259 
260         /***
261          * Returns the capacity of the pool.
262          *
263          * @return the capacity.
264          */
265         public int capacity()
266         {
267             return pool.capacity();
268         }
269 
270         /***
271          * Returns the size of the pool.
272          *
273          * @return the size.
274          */
275         public int size()
276         {
277             return pool.size();
278         }
279 
280         /***
281          * Returns a cached recycle method
282          * corresponding to the given signature.
283          *
284          * @param signature the signature.
285          * @return the recycle method or null.
286          */
287         private Method getRecycle(String[] signature)
288         {
289             ArrayList cache = recyclers;
290             if (cache != null)
291             {
292                 Method recycle;
293                 for (Iterator i = cache.iterator(); i.hasNext();)
294                 {
295                     recycle = ((Recycler) i.next()).match(signature);
296                     if (recycle != null)
297                     {
298                         return recycle;
299                     }
300                 }
301             }
302             return null;
303         }
304     }
305 
306     /***
307      * The default capacity of pools.
308      */
309     private int poolCapacity = DEFAULT_POOL_CAPACITY;
310 
311     /***
312      * The pool repository, one pool for each class.
313      */
314     private HashMap poolRepository = new HashMap();
315 
316     /***
317      * Constructs a Pool Service.
318      */
319     public TurbinePoolService()
320     {
321     }
322 
323     /***
324      * Initializes the service by setting the pool capacity.
325      *
326      * @param config initialization configuration.
327      * @throws InitializationException if initialization fails.
328      */
329     public void init()
330             throws InitializationException
331     {
332         Configuration conf = getConfiguration();
333 
334         int capacity = conf.getInt(POOL_CAPACITY_KEY,
335                 DEFAULT_POOL_CAPACITY);
336 
337         if (capacity <= 0)
338         {
339             throw new IllegalArgumentException("Capacity must be >0");
340         }
341         poolCapacity = capacity;
342 
343         debugPool = conf.getBoolean(POOL_DEBUG_KEY,
344                 POOL_DEBUG_DEFAULT);
345 
346         if (debugPool)
347         {
348             log.info("Activated Pool Debugging!");
349         }
350 
351         factoryService = TurbineFactory.getService();
352 
353         if (factoryService == null)
354         {
355             throw new InitializationException("Factory Service is not configured"
356                     + " but required for the Pool Service!");
357         }
358 
359         setInit(true);
360     }
361 
362     /***
363      * Gets an instance of a named class either from the pool
364      * or by calling the Factory Service if the pool is empty.
365      *
366      * @param className the name of the class.
367      * @return the instance.
368      * @throws TurbineException if recycling fails.
369      */
370     public Object getInstance(String className)
371             throws TurbineException
372     {
373         Object instance = pollInstance(className, null, null);
374         return (instance == null)
375                 ? factoryService.getInstance(className)
376                 : instance;
377     }
378 
379     /***
380      * Gets an instance of a named class either from the pool
381      * or by calling the Factory Service if the pool is empty.
382      * The specified class loader will be passed to the Factory Service.
383      *
384      * @param className the name of the class.
385      * @param loader the class loader.
386      * @return the instance.
387      * @throws TurbineException if recycling fails.
388      */
389     public Object getInstance(String className,
390                               ClassLoader loader)
391             throws TurbineException
392     {
393         Object instance = pollInstance(className, null, null);
394         return (instance == null)
395                 ? factoryService.getInstance(className, loader)
396                 : instance;
397     }
398 
399     /***
400      * Gets an instance of a named class either from the pool
401      * or by calling the Factory Service if the pool is empty.
402      * Parameters for its constructor are given as an array of objects,
403      * primitive types must be wrapped with a corresponding class.
404      *
405      * @param className the name of the class.
406      * @param loader the class loader.
407      * @param params an array containing the parameters of the constructor.
408      * @param signature an array containing the signature of the constructor.
409      * @return the instance.
410      * @throws TurbineException if recycling fails.
411      */
412     public Object getInstance(String className,
413                               Object[] params,
414                               String[] signature)
415             throws TurbineException
416     {
417         Object instance = pollInstance(className, params, signature);
418         return (instance == null)
419                 ? factoryService.getInstance(className, params, signature)
420                 : instance;
421     }
422 
423     /***
424      * Gets an instance of a named class either from the pool
425      * or by calling the Factory Service if the pool is empty.
426      * Parameters for its constructor are given as an array of objects,
427      * primitive types must be wrapped with a corresponding class.
428      * The specified class loader will be passed to the Factory Service.
429      *
430      * @param className the name of the class.
431      * @param loader the class loader.
432      * @param params an array containing the parameters of the constructor.
433      * @param signature an array containing the signature of the constructor.
434      * @return the instance.
435      * @throws TurbineException if recycling fails.
436      */
437     public Object getInstance(String className,
438                               ClassLoader loader,
439                               Object[] params,
440                               String[] signature)
441             throws TurbineException
442     {
443         Object instance = pollInstance(className, params, signature);
444         return (instance == null)
445                 ? factoryService.getInstance(className, loader, params, signature)
446                 : instance;
447     }
448 
449     /***
450      * Tests if specified class loaders are supported for a named class.
451      *
452      * @param className the name of the class.
453      * @return true if class loaders are supported, false otherwise.
454      * @throws TurbineException if test fails.
455      * @deprecated Use TurbineFactory.isLoaderSupported(className);
456      */
457     public boolean isLoaderSupported(String className)
458             throws TurbineException
459     {
460         return factoryService.isLoaderSupported(className);
461     }
462 
463     /***
464      * Gets an instance of a specified class either from the pool
465      * or by instatiating from the class if the pool is empty.
466      *
467      * @param clazz the class.
468      * @return the instance.
469      * @throws TurbineException if recycling fails.
470      */
471     public Object getInstance(Class clazz)
472             throws TurbineException
473     {
474         Object instance = pollInstance(clazz.getName(), null, null);
475         return (instance == null)
476                 ? factoryService.getInstance(clazz.getName())
477                 : instance;
478     }
479 
480     /***
481      * Gets an instance of a specified class either from the pool
482      * or by instatiating from the class if the pool is empty.
483      *
484      * @param clazz the class.
485      * @param params an array containing the parameters of the constructor.
486      * @param signature an array containing the signature of the constructor.
487      * @return the instance.
488      * @throws TurbineException if recycling fails.
489      */
490     public Object getInstance(Class clazz,
491                               Object params[],
492                               String signature[])
493             throws TurbineException
494     {
495         Object instance = pollInstance(clazz.getName(), params, signature);
496         return (instance == null)
497                 ? factoryService.getInstance(clazz.getName(), params, signature)
498                 : instance;
499     }
500 
501     /***
502      * Puts a used object back to the pool. Objects implementing
503      * the Recyclable interface can provide a recycle method to
504      * be called when they are reused and a dispose method to be
505      * called when they are returned to the pool.
506      *
507      * @param instance the object instance to recycle.
508      * @return true if the instance was accepted.
509      */
510     public boolean putInstance(Object instance)
511     {
512         if (instance != null)
513         {
514             HashMap repository = poolRepository;
515             String className = instance.getClass().getName();
516             PoolBuffer pool = (PoolBuffer) repository.get(className);
517             if (pool == null)
518             {
519                 pool = new PoolBuffer(getCapacity(className));
520                 repository = (HashMap) repository.clone();
521                 repository.put(className, pool);
522                 poolRepository = repository;
523 
524                 if (instance instanceof ArrayCtorRecyclable)
525                 {
526                     pool.setArrayCtorRecyclable(true);
527                 }
528             }
529             return pool.offer(instance);
530         }
531         else
532         {
533             return false;
534         }
535     }
536 
537     /***
538      * Gets the capacity of the pool for a named class.
539      *
540      * @param className the name of the class.
541      */
542     public int getCapacity(String className)
543     {
544         PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
545         if (pool == null)
546         {
547             /* Check class specific capacity. */
548             int capacity;
549 
550             Configuration conf = getConfiguration();
551             capacity = conf.getInt(
552                     POOL_CAPACITY_KEY + '.' + className,
553                     poolCapacity);
554             capacity = (capacity <= 0) ? poolCapacity : capacity;
555             return capacity;
556         }
557         else
558         {
559             return pool.capacity();
560         }
561     }
562 
563     /***
564      * Sets the capacity of the pool for a named class.
565      * Note that the pool will be cleared after the change.
566      *
567      * @param className the name of the class.
568      * @param capacity the new capacity.
569      */
570     public void setCapacity(String className,
571                             int capacity)
572     {
573         HashMap repository = poolRepository;
574         repository = (repository != null)
575                 ? (HashMap) repository.clone() : new HashMap();
576 
577         capacity = (capacity <= 0) ? poolCapacity : capacity;
578 
579         repository.put(className, new PoolBuffer(capacity));
580         poolRepository = repository;
581     }
582 
583     /***
584      * Gets the current size of the pool for a named class.
585      *
586      * @param className the name of the class.
587      */
588     public int getSize(String className)
589     {
590         PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
591         return (pool != null) ? pool.size() : 0;
592     }
593 
594     /***
595      * Clears instances of a named class from the pool.
596      *
597      * @param className the name of the class.
598      */
599     public void clearPool(String className)
600     {
601         HashMap repository = poolRepository;
602         if (repository.get(className) != null)
603         {
604             repository = (HashMap) repository.clone();
605             repository.remove(className);
606             poolRepository = repository;
607         }
608     }
609 
610     /***
611      * Clears all instances from the pool.
612      */
613     public void clearPool()
614     {
615         poolRepository = new HashMap();
616     }
617 
618     /***
619      * Polls and recycles an object of the named class from the pool.
620      *
621      * @param className the name of the class.
622      * @param params an array containing the parameters of the constructor.
623      * @param signature an array containing the signature of the constructor.
624      * @return the object or null.
625      * @throws TurbineException if recycling fails.
626      */
627     private Object pollInstance(String className,
628                                 Object[] params,
629                                 String[] signature)
630             throws TurbineException
631     {
632         PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
633         return (pool != null) ? pool.poll(params, signature) : null;
634     }
635 }