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&ouml;rg Schaible
069     * @author Aslak Helles&oslash;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&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    }