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 }