View Javadoc
1   package org.apache.fulcrum.pool;
2   
3   import java.util.HashMap;
4   
5   /*
6    * Licensed to the Apache Software Foundation (ASF) under one
7    * or more contributor license agreements.  See the NOTICE file
8    * distributed with this work for additional information
9    * regarding copyright ownership.  The ASF licenses this file
10   * to you under the Apache License, Version 2.0 (the
11   * "License"); you may not use this file except in compliance
12   * with the License.  You may obtain a copy of the License at
13   *
14   *   http://www.apache.org/licenses/LICENSE-2.0
15   *
16   * Unless required by applicable law or agreed to in writing,
17   * software distributed under the License is distributed on an
18   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19   * KIND, either express or implied.  See the License for the
20   * specific language governing permissions and limitations
21   * under the License.
22   */
23  
24  import java.util.Map;
25  
26  import org.apache.avalon.framework.activity.Disposable;
27  import org.apache.avalon.framework.activity.Initializable;
28  import org.apache.avalon.framework.configuration.Configurable;
29  import org.apache.avalon.framework.configuration.Configuration;
30  import org.apache.avalon.framework.logger.AbstractLogEnabled;
31  import org.apache.avalon.framework.service.ServiceManager;
32  import org.apache.avalon.framework.service.Serviceable;
33  import org.apache.fulcrum.factory.FactoryException;
34  import org.apache.fulcrum.factory.FactoryService;
35  
36  /**
37   * The Pool Service extends the Factory Service by adding support for pooling
38   * instantiated objects. When a new instance is requested, the service first
39   * checks its pool if one is available. If the the pool is empty, a new instance
40   * will be requested from the FactoryService.
41   *
42   * For objects implementing the Recyclable interface, a recycle method will be
43   * called, when they taken from the pool, and a dispose method, when they are
44   * returned to the pool.
45   *
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   * avalon.component name="pool" lifestyle="transient"
51   * avalon.service type="org.apache.fulcrum.pool.PoolService"
52   */
53  public class DefaultPoolService extends AbstractLogEnabled
54  		implements PoolService, Serviceable, Disposable, Initializable, Configurable {
55  	/**
56  	 * The property specifying the pool capacity.
57  	 */
58  	public static final String POOL_CAPACITY = "capacity";
59  	
60  	/**
61  	 * The default capacity of pools.
62  	 */
63  	private int poolCapacity = DEFAULT_POOL_CAPACITY;
64  	
65  	/**
66  	 * The pool repository, one pool for each class.
67  	 */
68  	private HashMap<String, PoolBuffer> poolRepository = new HashMap<>();
69  	private Map<String, Integer> capacityMap;
70  	private FactoryService factoryService;
71  	private ServiceManager manager;
72  
73  	/**
74  	 * Gets an instance of a named class either from the pool or by calling the
75  	 * Factory Service if the pool is empty.
76  	 *
77  	 * @param className the name of the class.
78  	 * @return the instance.
79  	 * @throws PoolException if recycling fails.
80  	 */
81  	public <T> T getInstance(String className) throws PoolException 
82  	{
83  		try 
84  		{
85  			T instance = pollInstance(className, null, null);
86  			return instance == null ? getFactory().getInstance(className) : instance;
87  		} 
88  		catch (FactoryException fe) 
89  		{
90  			throw new PoolException(fe);
91  		}
92  	}
93  
94  	/**
95  	 * Gets an instance of a named class either from the pool or by calling the
96  	 * Factory Service if the pool is empty. The specified class loader will be
97  	 * passed to the Factory Service.
98  	 *
99  	 * @param className the name of the class.
100 	 * @param loader    the class loader.
101 	 * @return the instance.
102 	 * @throws PoolException if recycling fails.
103 	 */
104 	public Object getInstance(String className, ClassLoader loader) throws PoolException 
105 	{
106 		try 
107 		{
108 			Object instance = pollInstance(className, null, null);
109 			return instance == null ? getFactory().getInstance(className, loader) : instance;
110 		} 
111 		catch (FactoryException fe) 
112 		{
113 			throw new PoolException(fe);
114 		}
115 	}
116 
117 	/**
118 	 * Gets an instance of a named class either from the pool or by calling the
119 	 * Factory Service if the pool is empty. Parameters for its constructor are
120 	 * given as an array of objects, primitive types must be wrapped with a
121 	 * corresponding class.
122 	 *
123 	 * @param className the name of the class.
124 	 * @param params    an array containing the parameters of the constructor.
125 	 * @param signature an array containing the signature of the constructor.
126 	 * @return the instance.
127 	 * @throws PoolException if recycling fails.
128 	 */
129 	public Object getInstance(String className, Object[] params, String[] signature) throws PoolException 
130 	{
131 		try 
132 		{
133 			Object instance = pollInstance(className, params, signature);
134 			return instance == null ? getFactory().getInstance(className, params, signature) : instance;
135 		} 
136 		catch (FactoryException fe) 
137 		{
138 			throw new PoolException(fe);
139 		}
140 	}
141 
142 	/**
143 	 * Gets an instance of a named class either from the pool or by calling the
144 	 * Factory Service if the pool is empty. Parameters for its constructor are
145 	 * given as an array of objects, primitive types must be wrapped with a
146 	 * corresponding class. The specified class loader will be passed to the Factory
147 	 * Service.
148 	 *
149 	 * @param className the name of the class.
150 	 * @param loader    the class loader.
151 	 * @param params    an array containing the parameters of the constructor.
152 	 * @param signature an array containing the signature of the constructor.
153 	 * @return the instance.
154 	 * @throws PoolException if recycling fails.
155 	 */
156 	public Object getInstance(String className, ClassLoader loader, Object[] params, String[] signature)
157 			throws PoolException 
158 	{
159 		try 
160 		{
161 			Object instance = pollInstance(className, params, signature);
162 			return instance == null ? getFactory().getInstance(className, loader, params, signature) : instance;
163 		} 
164 		catch (FactoryException fe) 
165 		{
166 			throw new PoolException(fe);
167 		}
168 	}
169 
170 	/**
171 	 * Tests if specified class loaders are supported for a named class.
172 	 *
173 	 * @param className the name of the class.
174 	 * @return true if class loaders are supported, false otherwise.
175 	 * @throws FactoryException if test fails.
176 	 */
177 	public boolean isLoaderSupported(String className) throws FactoryException 
178 	{
179 		return getFactory().isLoaderSupported(className);
180 	}
181 
182 	/**
183 	 * Gets an instance of a specified class either from the pool or by instatiating
184 	 * from the class if the pool is empty.
185 	 *
186 	 * @param <T> type of the class
187 	 * @param clazz the class.
188 	 * @return the instance.
189 	 * @throws PoolException if recycling fails.
190 	 */
191 	@SuppressWarnings("unchecked")
192 	public <T> T getInstance(Class<?> clazz) throws PoolException 
193 	{
194 		try 
195 		{
196 			T instance = pollInstance(clazz.getName(), null, null);
197 			return instance == null ? (T) factoryService.getInstance(clazz) : instance;
198 		} 
199 		catch (FactoryException fe) 
200 		{
201 			throw new PoolException(fe);
202 		}
203 	}
204 
205 	/**
206 	 * Gets an instance of a specified class either from the pool or by instatiating
207 	 * from the class if the pool is empty.
208 	 *
209 	 * @param clazz     the class.
210 	 * @param params    an array containing the parameters of the constructor.
211 	 * @param signature an array containing the signature of the constructor.
212 	 * @return the instance.
213 	 * @throws PoolException if recycling fails.
214 	 */
215 	public <T> T getInstance(Class<?> clazz, Object params[], String signature[]) throws PoolException 
216 	{
217 		try 
218 		{
219 			T instance = pollInstance(clazz.getName(), params, signature);
220 			
221 			// TODO There is a whacky .toString() on the clazz object, 
222 			// but otherwise it won't compile
223 			return instance == null ? getFactory().getInstance(clazz.toString(), params, signature) : instance;
224 			
225 		} 
226 		catch (FactoryException fe) 
227 		{
228 			throw new PoolException(fe);
229 		}
230 	}
231 
232 	/**
233 	 * Puts a used object back to the pool. Objects implementing the Recyclable
234 	 * interface can provide a recycle method to be called when they are reused and
235 	 * a dispose method to be called when they are returned to the pool.
236 	 *
237 	 * @param instance the object instance to recycle.
238 	 * @return true if the instance was accepted.
239 	 */
240 	@SuppressWarnings("unchecked")
241 	public boolean putInstance(Object instance) 
242 	{
243 		if (instance != null) 
244 		{
245 			HashMap<String, PoolBuffer> repository = poolRepository;
246 			String className = instance.getClass().getName();
247 			PoolBuffer pool = (PoolBuffer) repository.get(className);
248 			if (pool == null) 
249 			{
250 				pool = new PoolBuffer(getCapacity(className));
251 				repository = (HashMap<String, PoolBuffer>) repository.clone();
252 				repository.put(className, pool);
253 				poolRepository = repository;
254 				if (instance instanceof ArrayCtorRecyclable) 
255 				{
256 					pool.setArrayCtorRecyclable(true);
257 				}
258 			}
259 			return pool.offer(instance);
260 		} else {
261 			return false;
262 		}
263 	}
264 
265 	/**
266 	 * Gets the capacity of the pool for a named class.
267 	 *
268 	 * @param className the name of the class.
269 	 */
270 	public int getCapacity(String className) 
271 	{
272 		PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
273 		if (pool == null) 
274 		{
275 			/* Check class specific capacity. */
276 			int capacity = poolCapacity;
277 			if (capacityMap != null) 
278 			{
279 				Integer cap = (Integer) capacityMap.get(className);
280 				if (cap != null) 
281 				{
282 					capacity = cap.intValue();
283 				}
284 			}
285 			return capacity;
286 		} else {
287 			return pool.capacity();
288 		}
289 	}
290 
291 	/**
292 	 * Sets the capacity of the pool for a named class. Note that the pool will be
293 	 * cleared after the change.
294 	 *
295 	 * @param className the name of the class.
296 	 * @param capacity  the new capacity.
297 	 */
298 	@SuppressWarnings("unchecked")
299 	public void setCapacity(String className, int capacity) 
300 	{
301 		HashMap<String, PoolBuffer> repository = poolRepository;
302 		repository = repository != null ? (HashMap<String, PoolBuffer>) repository.clone()
303 				: new HashMap<String, PoolBuffer>();
304 		repository.put(className, new PoolBuffer(capacity));
305 		poolRepository = repository;
306 	}
307 
308 	/**
309 	 * Gets the current size of the pool for a named class.
310 	 *
311 	 * @param className the name of the class.
312 	 */
313 	public int getSize(String className) 
314 	{
315 		PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
316 		return pool != null ? pool.size() : 0;
317 	}
318 
319 	/**
320 	 * Clears instances of a named class from the pool.
321 	 *
322 	 * @param className the name of the class.
323 	 */
324 	@SuppressWarnings("unchecked")
325 	public void clearPool(String className) 
326 	{
327 		HashMap<String, PoolBuffer> repository = poolRepository;
328 		if (repository.get(className) != null) 
329 		{
330 			repository = (HashMap<String, PoolBuffer>) repository.clone();
331 			repository.remove(className);
332 			poolRepository = repository;
333 		}
334 	}
335 
336 	/**
337 	 * Clears all instances from the pool.
338 	 */
339 	public void clearPool() 
340 	{
341 		poolRepository = new HashMap<String, PoolBuffer>();
342 	}
343 
344 	/**
345 	 * Polls and recycles an object of the named class from the pool.
346 	 *
347 	 * @param className the name of the class.
348 	 * @param params    an array containing the parameters of the constructor.
349 	 * @param signature an array containing the signature of the constructor.
350 	 * @return the object or null.
351 	 * @throws PoolException if recycling fails.
352 	 */
353 	private <T> T pollInstance(String className, Object[] params, String[] signature) throws PoolException 
354 	{
355 		PoolBuffer pool = (PoolBuffer) poolRepository.get(className);
356 		return pool != null ? pool.poll(params, signature, factoryService) : null;
357 	}
358 
359 	/**
360 	 * Gets the factory service.
361 	 *
362 	 * @return the factory service.
363 	 */
364 	protected FactoryService getFactory() 
365 	{
366 		return factoryService;
367 	}
368 
369 	// ---------------- Avalon Lifecycle Methods ---------------------
370 	/* (non-Javadoc)
371 	 * Avalon component lifecycle method
372 	 * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
373 	 */
374 	public void configure(Configuration conf) 
375 	{
376 		final Configuration capacities = conf.getChild(POOL_CAPACITY, false);
377 		if (capacities != null) 
378 		{
379 			Configuration defaultConf = capacities.getChild("default");
380 			int capacity = defaultConf.getValueAsInteger(DEFAULT_POOL_CAPACITY);
381 			if (capacity <= 0) 
382 			{
383 				throw new IllegalArgumentException("Capacity must be >0");
384 			}
385 			poolCapacity = capacity;
386 			Configuration[] nameVal = capacities.getChildren();
387 			for (int i = 0; i < nameVal.length; i++) 
388 			{
389 				String key = nameVal[i].getName();
390 				if (!"default".equals(key)) 
391 				{
392 					capacity = nameVal[i].getValueAsInteger(poolCapacity);
393 					if (capacity < 0) 
394 					{
395 						capacity = poolCapacity;
396 					}
397 					
398 					if (capacityMap == null) 
399 					{
400 						capacityMap = new HashMap<String, Integer>();
401 					}
402 					
403 					capacityMap.put(key, capacity);
404 				}
405 			}
406 		}
407 	}
408 
409 	/**
410 	 * Avalon component lifecycle method
411 	 * 
412 	 * {@link org.apache.fulcrum.factory.FactoryService}
413 	 * 
414 	 * @param manager the service manager
415 	 */
416 	public void service(ServiceManager manager) 
417 	{
418 		this.manager = manager;
419 	}
420 
421 	/**
422 	 * Avalon component lifecycle method Initializes the service by loading default
423 	 * class loaders and customized object factories.
424 	 *
425 	 * @throws Exception if initialization fails.
426 	 */
427 	public void initialize() throws Exception 
428 	{
429 		try 
430 		{
431 			factoryService = (FactoryService) manager.lookup(FactoryService.ROLE);
432 		} 
433 		catch (Exception e) 
434 		{
435 			throw new Exception("DefaultPoolService.initialize: Failed to get a Factory object", e);
436 		}
437 	}
438 
439 	/**
440 	 * Avalon component lifecycle method
441 	 */
442 	public void dispose() 
443 	{
444 		if (factoryService != null) 
445 		{
446 			manager.release(factoryService);
447 		}
448 		factoryService = null;
449 		manager = null;
450 	}
451 }