    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    }