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