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 }