001 /***************************************************************************** 002 * Copyright (C) NanoContainer Organization. All rights reserved. * 003 * ------------------------------------------------------------------------- * 004 * The software in this package is published under the terms of the BSD * 005 * style license a copy of which has been included with this distribution in * 006 * the LICENSE.txt file. * 007 * * 008 * Original code by Aslak Hellesoy & Joerg Schaible * 009 *****************************************************************************/ 010 package org.picocontainer.gems.behaviors; 011 012 import java.io.ByteArrayOutputStream; 013 import java.io.IOException; 014 import java.io.NotSerializableException; 015 import java.io.ObjectInputStream; 016 import java.io.ObjectOutputStream; 017 import java.io.Serializable; 018 import java.util.ArrayList; 019 import java.util.List; 020 import java.lang.reflect.Type; 021 022 import org.picocontainer.ComponentAdapter; 023 import org.picocontainer.PicoContainer; 024 import org.picocontainer.behaviors.AbstractBehavior; 025 import org.picocontainer.LifecycleStrategy; 026 import org.picocontainer.PicoCompositionException; 027 028 import com.thoughtworks.proxy.ProxyFactory; 029 import com.thoughtworks.proxy.factory.StandardProxyFactory; 030 import com.thoughtworks.proxy.kit.NoOperationResetter; 031 import com.thoughtworks.proxy.kit.Resetter; 032 import com.thoughtworks.proxy.toys.nullobject.Null; 033 import com.thoughtworks.proxy.toys.pool.Pool; 034 035 036 /** 037 * {@link ComponentAdapter} implementation that pools components. 038 * <p> 039 * The implementation utilizes a delegated ComponentAdapter to create the instances of the pool. The 040 * pool can be configured to grow unlimited or to a maximum size. If a component is requested from 041 * this adapter, the implementation returns an available instance from the pool or will create a 042 * new one, if the maximum pool size is not reached yet. If none is available, the implementation 043 * can wait a defined time for a returned object before it throws a {@link PoolException}. 044 * </p> 045 * <p> 046 * This implementation uses the {@link Pool} toy from the <a 047 * href="http://proxytoys.codehaus.org">ProxyToys</a> project. This ensures, that any component, 048 * that is out of scope will be automatically returned to the pool by the garbage collector. 049 * Additionally will every component instance also implement 050 * {@link com.thoughtworks.proxy.toys.pool.Poolable}, that can be used to return the instance 051 * manually. After returning an instance it should not be used in client code anymore. 052 * </p> 053 * <p> 054 * Before a returning object is added to the available instances of the pool again, it should be 055 * reinitialized to a normalized state. By providing a proper Resetter implementation this can be 056 * done automatically. If the object cannot be reused anymore it can also be dropped and the pool 057 * may request a new instance. 058 * </p> 059 * <p> 060 * The pool supports components with a lifecycle. If the delegated {@link ComponentAdapter} 061 * implements a {@link LifecycleStrategy}, any component retrieved form the pool will be started 062 * before and stopped again, when it returns back into the pool. Also if a component cannot be 063 * resetted it will automatically be disposed. If the container of the pool is disposed, that any 064 * returning object is also disposed and will not return to the pool anymore. Note, that current 065 * implementation cannot dispose pooled objects. 066 * </p> 067 * 068 * @author Jörg Schaible 069 * @author Aslak Hellesøy 070 */ 071 @SuppressWarnings("serial") 072 public final class Pooled<T> extends AbstractBehavior<T> { 073 074 075 076 /** 077 * Context of the Pooled used to initialize it. 078 * 079 * @author Jörg Schaible 080 */ 081 public static interface Context { 082 /** 083 * Retrieve the maximum size of the pool. An implementation may return the maximum value or 084 * {@link Pooled#UNLIMITED_SIZE} for <em>unlimited</em> growth. 085 * 086 * @return the maximum pool size 087 */ 088 int getMaxSize(); 089 090 /** 091 * Retrieve the maximum number of milliseconds to wait for a returned element. An 092 * implementation may return alternatively {@link Pooled#BLOCK_ON_WAIT} or 093 * {@link Pooled#FAIL_ON_WAIT}. 094 * 095 * @return the maximum number of milliseconds to wait 096 */ 097 int getMaxWaitInMilliseconds(); 098 099 /** 100 * Allow the implementation to invoke the garbace collector manually if the pool is 101 * exhausted. 102 * 103 * @return <code>true</code> for an internal call to {@link System#gc()} 104 */ 105 boolean autostartGC(); 106 107 /** 108 * Retrieve the ProxyFactory to use to create the pooling proxies. 109 * 110 * @return the {@link ProxyFactory} 111 */ 112 ProxyFactory getProxyFactory(); 113 114 /** 115 * Retrieve the {@link Resetter} of the objects returning to the pool. 116 * 117 * @return the Resetter instance 118 */ 119 Resetter getResetter(); 120 121 /** 122 * Retrieve the serialization mode of the pool. Following values are possible: 123 * <ul> 124 * <li>{@link Pool#SERIALIZATION_STANDARD}</li> 125 * <li>{@link Pool#SERIALIZATION_NONE}</li> 126 * <li>{@link Pool#SERIALIZATION_FORCE}</li> 127 * </ul> 128 * 129 * @return the serialization mode 130 */ 131 int getSerializationMode(); 132 } 133 134 /** 135 * The default context for a Pooled. 136 * 137 * @author Jörg Schaible 138 */ 139 public static class DefaultContext implements Context { 140 141 /** 142 * {@inheritDoc} Returns {@link Pooled#DEFAULT_MAX_SIZE}. 143 */ 144 public int getMaxSize() { 145 return DEFAULT_MAX_SIZE; 146 } 147 148 /** 149 * {@inheritDoc} Returns {@link Pooled#FAIL_ON_WAIT}. 150 */ 151 public int getMaxWaitInMilliseconds() { 152 return FAIL_ON_WAIT; 153 } 154 155 /** 156 * {@inheritDoc} Returns <code>false</code>. 157 */ 158 public boolean autostartGC() { 159 return false; 160 } 161 162 /** 163 * {@inheritDoc} Returns a {@link StandardProxyFactory}. 164 */ 165 public ProxyFactory getProxyFactory() { 166 return new StandardProxyFactory(); 167 } 168 169 /** 170 * {@inheritDoc} Returns the {@link Pooled#DEFAULT_RESETTER}. 171 */ 172 public Resetter getResetter() { 173 return DEFAULT_RESETTER; 174 } 175 176 /** 177 * {@inheritDoc} Returns {@link Pool#SERIALIZATION_STANDARD}. 178 */ 179 public int getSerializationMode() { 180 return Pool.SERIALIZATION_STANDARD; 181 } 182 183 } 184 185 /** 186 * <code>UNLIMITED_SIZE</code> is the value to set the maximum size of the pool to unlimited ({@link Integer#MAX_VALUE} 187 * in fact). 188 */ 189 public static final int UNLIMITED_SIZE = Integer.MAX_VALUE; 190 /** 191 * <code>DEFAULT_MAX_SIZE</code> is the default size of the pool. 192 */ 193 public static final int DEFAULT_MAX_SIZE = 8; 194 /** 195 * <code>BLOCK_ON_WAIT</code> forces the pool to wait until an object of the pool is returning 196 * in case none is immediately available. 197 */ 198 public static final int BLOCK_ON_WAIT = 0; 199 /** 200 * <code>FAIL_ON_WAIT</code> forces the pool to fail none is immediately available. 201 */ 202 public static final int FAIL_ON_WAIT = -1; 203 /** 204 * <code>DEFAULT_RESETTER</code> is a {@link NoOperationResetter} that is used by default. 205 */ 206 public static final Resetter DEFAULT_RESETTER = new NoOperationResetter(); 207 208 private int maxPoolSize; 209 private int waitMilliSeconds; 210 private Pool pool; 211 private int serializationMode; 212 private boolean autostartGC; 213 private boolean started; 214 private boolean disposed; 215 private boolean delegateHasLifecylce; 216 private transient List<Object> components; 217 218 /** 219 * Construct a Pooled. Remember, that the implementation will request new 220 * components from the delegate as long as no component instance is available in the pool and 221 * the maximum pool size is not reached. Therefore the delegate may not return the same 222 * component instance twice. Ensure, that the used {@link ComponentAdapter} does not cache. 223 * 224 * @param delegate the delegated ComponentAdapter 225 * @param context the {@link Context} of the pool 226 * @throws IllegalArgumentException if the maximum pool size or the serialization mode is 227 * invalid 228 */ 229 public Pooled(final ComponentAdapter delegate, final Context context) { 230 super(delegate); 231 this.maxPoolSize = context.getMaxSize(); 232 this.waitMilliSeconds = context.getMaxWaitInMilliseconds(); 233 this.autostartGC = context.autostartGC(); 234 this.serializationMode = context.getSerializationMode(); 235 if (maxPoolSize <= 0) { 236 throw new IllegalArgumentException("Invalid maximum pool size"); 237 } 238 started = false; 239 disposed = false; 240 delegateHasLifecylce = delegate instanceof LifecycleStrategy 241 && ((LifecycleStrategy)delegate) 242 .hasLifecycle(delegate.getComponentImplementation()); 243 components = new ArrayList<Object>(); 244 245 final Class type = delegate.getComponentKey() instanceof Class ? (Class)delegate 246 .getComponentKey() : delegate.getComponentImplementation(); 247 final Resetter resetter = context.getResetter(); 248 this.pool = new Pool(type, delegateHasLifecylce ? new LifecycleResetter( 249 this, resetter) : resetter, context.getProxyFactory(), serializationMode); 250 } 251 252 /** 253 * Construct an empty ComponentAdapter, used for serialization with reflection only. 254 * 255 */ 256 protected Pooled() { 257 // TODO super class should support standard ctor 258 super((ComponentAdapter)Null.object(ComponentAdapter.class)); 259 } 260 261 /** 262 * {@inheritDoc} 263 * <p> 264 * As long as the maximum size of the pool is not reached and the pool is exhausted, the 265 * implementation will request its delegate for a new instance, that will be managed by the 266 * pool. Only if the maximum size of the pool is reached, the implementation may wait (depends 267 * on the initializing {@link Context}) for a returning object. 268 * </p> 269 * 270 * @throws PoolException if the pool is exhausted or waiting for a returning object timed out or 271 * was interrupted 272 */ 273 @Override 274 public T getComponentInstance(final PicoContainer container, final Type into) { 275 if (delegateHasLifecylce) { 276 if (disposed) throw new IllegalStateException("Already disposed"); 277 } 278 T componentInstance; 279 long now = System.currentTimeMillis(); 280 boolean gc = autostartGC; 281 while (true) { 282 synchronized (pool) { 283 componentInstance = (T) pool.get(); 284 if (componentInstance != null) { 285 break; 286 } 287 if (maxPoolSize > pool.size()) { 288 final Object component = super.getComponentInstance(container, into); 289 if (delegateHasLifecylce) { 290 components.add(component); 291 if (started) { 292 start(component); 293 } 294 } 295 pool.add(component); 296 } else if (!gc) { 297 long after = System.currentTimeMillis(); 298 if (waitMilliSeconds < 0) { 299 throw new PoolException("Pool exhausted"); 300 } 301 if (waitMilliSeconds > 0 && after - now > waitMilliSeconds) { 302 throw new PoolException("Time out wating for returning object into pool"); 303 } 304 try { 305 pool.wait(waitMilliSeconds); // Note, the pool notifies after an object 306 // was returned 307 } catch (InterruptedException e) { 308 // give the client code of the current thread a chance to abort also 309 Thread.currentThread().interrupt(); 310 throw new PoolException( 311 "Interrupted waiting for returning object into the pool", e); 312 } 313 } else { 314 System.gc(); 315 gc = false; 316 } 317 } 318 } 319 return componentInstance; 320 } 321 322 public String getDescriptor() { 323 return "Pooled"; 324 } 325 326 /** 327 * Retrieve the current size of the pool. The returned value reflects the number of all managed 328 * components. 329 * 330 * @return the number of components. 331 */ 332 public int size() { 333 return pool.size(); 334 } 335 336 static final class LifecycleResetter implements Resetter, Serializable { 337 private final Resetter delegate; 338 private final Pooled adapter; 339 340 LifecycleResetter(final Pooled adapter, final Resetter delegate) { 341 this.adapter = adapter; 342 this.delegate = delegate; 343 } 344 345 public boolean reset(final Object object) { 346 final boolean result = delegate.reset(object); 347 if (!result || adapter.disposed) { 348 if (adapter.started) { 349 adapter.stop(object); 350 } 351 adapter.components.remove(object); 352 if (!adapter.disposed) { 353 adapter.dispose(object); 354 } 355 } 356 return result && !adapter.disposed; 357 } 358 359 } 360 361 /** 362 * Start of the container ensures that at least one pooled component has been started. Applies 363 * only if the delegated {@link ComponentAdapter} supports a lifecylce by implementing 364 * {@link LifecycleStrategy}. 365 * 366 * @throws IllegalStateException if pool was already disposed 367 */ 368 @Override 369 public void start(final PicoContainer container) { 370 if (delegateHasLifecylce) { 371 if (started) throw new IllegalStateException("Already started"); 372 if (disposed) throw new IllegalStateException("Already disposed"); 373 for (Object component : components) { 374 start(component); 375 } 376 started = true; 377 if (pool.size() == 0) { 378 getComponentInstance(container, ComponentAdapter.NOTHING.class); 379 } 380 } 381 } 382 383 /** 384 * Stop of the container has no effect for the pool. Applies only if the delegated 385 * {@link ComponentAdapter} supports a lifecylce by implementing {@link org.picocontainer.LifecycleStrategy}. 386 * 387 * @throws IllegalStateException if pool was already disposed 388 */ 389 @Override 390 public void stop(final PicoContainer container) { 391 if (delegateHasLifecylce) { 392 if (!started) throw new IllegalStateException("Not started yet"); 393 if (disposed) throw new IllegalStateException("Already disposed"); 394 for (Object component : components) { 395 stop(component); 396 } 397 started = false; 398 } 399 } 400 401 /** 402 * Dispose of the container will dispose all returning objects. They will not be added to the 403 * pool anymore. Applies only if the delegated {@link ComponentAdapter} supports a lifecylce by 404 * implementing {@link LifecycleStrategy}. 405 * 406 * @throws IllegalStateException if pool was already disposed 407 */ 408 @Override 409 public void dispose(final PicoContainer container) { 410 if (delegateHasLifecylce) { 411 if (started) throw new IllegalStateException("Not stopped yet"); 412 if (disposed) throw new IllegalStateException("Already disposed"); 413 disposed = true; 414 for (Object component : components) { 415 dispose(component); 416 } 417 // TODO: Release pooled components and clear collection 418 } 419 } 420 421 private synchronized void writeObject(final ObjectOutputStream out) throws IOException { 422 out.defaultWriteObject(); 423 int mode = serializationMode; 424 if (mode == Pool.SERIALIZATION_FORCE && components.size() > 0) { 425 try { 426 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 427 final ObjectOutputStream testStream = new ObjectOutputStream(buffer); 428 testStream.writeObject(components); // force NotSerializableException 429 testStream.close(); 430 } catch (final NotSerializableException e) { 431 mode = Pool.SERIALIZATION_NONE; 432 } 433 } 434 if (mode == Pool.SERIALIZATION_STANDARD) { 435 out.writeObject(components); 436 } else { 437 out.writeObject(new ArrayList()); 438 } 439 } 440 441 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 442 in.defaultReadObject(); 443 components = (List<Object>)in.readObject(); 444 } 445 446 /** 447 * Exception thrown from the Pooled. Only thrown if the interaction with the internal pool fails. 448 * 449 * @author Jörg Schaible 450 */ 451 public static class PoolException extends PicoCompositionException { 452 453 454 /** 455 * Construct a PoolException with an explaining message and a originalting cause. 456 * 457 * @param message the explaining message 458 * @param cause the originating cause 459 */ 460 public PoolException(final String message, final Throwable cause) { 461 super(message, cause); 462 } 463 464 /** 465 * Construct a PoolException with an explaining message. 466 * 467 * @param message the explaining message 468 */ 469 public PoolException(final String message) { 470 super(message); 471 } 472 473 } 474 475 }