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 *****************************************************************************/
010package org.picocontainer.gems.behaviors;
011
012import java.io.ByteArrayOutputStream;
013import java.io.IOException;
014import java.io.NotSerializableException;
015import java.io.ObjectInputStream;
016import java.io.ObjectOutputStream;
017import java.io.Serializable;
018import java.util.ArrayList;
019import java.util.List;
020import java.lang.reflect.Type;
021
022import org.picocontainer.ComponentAdapter;
023import org.picocontainer.PicoContainer;
024import org.picocontainer.behaviors.AbstractBehavior;
025import org.picocontainer.LifecycleStrategy;
026import org.picocontainer.PicoCompositionException;
027
028import com.thoughtworks.proxy.ProxyFactory;
029import com.thoughtworks.proxy.factory.StandardProxyFactory;
030import com.thoughtworks.proxy.kit.NoOperationResetter;
031import com.thoughtworks.proxy.kit.Resetter;
032import com.thoughtworks.proxy.toys.nullobject.Null;
033import 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&ouml;rg Schaible
069 * @author Aslak Helles&oslash;y
070 */
071@SuppressWarnings("serial")
072public final class Pooled<T> extends AbstractBehavior<T> {
073
074
075
076    /**
077     * Context of the Pooled used to initialize it.
078     * 
079     * @author J&ouml;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&ouml;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&ouml;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}