    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.pool2.impl;
    018
    019import java.lang.ref.Reference;
    020import java.lang.ref.ReferenceQueue;
    021import java.lang.ref.SoftReference;
    022import java.util.ArrayList;
    023import java.util.Iterator;
    024import java.util.NoSuchElementException;
    025
    026import org.apache.commons.pool2.BaseObjectPool;
    027import org.apache.commons.pool2.ObjectPool;
    028import org.apache.commons.pool2.PoolUtils;
    029import org.apache.commons.pool2.PooledObjectFactory;
    030
    031/**
    032 * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}.
    033 * <p>
    034 * This class is intended to be thread-safe.
    035 * </p>
    036 *
    037 * @param <T>
    038 *            Type of element pooled in this pool.
    039 *
    040 * @since 2.0
    041 */
    042public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> {
    043
    044    /** Factory to source pooled objects */
    045    private final PooledObjectFactory<T> factory;
    046
    047    /**
    048     * Queue of broken references that might be able to be removed from
    049     * {@code _pool}. This is used to help {@link #getNumIdle()} be more
    050     * accurate with minimal performance overhead.
    051     */
    052    private final ReferenceQueue<T> refQueue = new ReferenceQueue<>();
    053
    054    /** Count of instances that have been checkout out to pool clients */
    055    private int numActive = 0; // @GuardedBy("this")
    056
    057    /** Total number of instances that have been destroyed */
    058    private long destroyCount = 0; // @GuardedBy("this")
    059
    060
    061    /** Total number of instances that have been created */
    062    private long createCount = 0; // @GuardedBy("this")
    063
    064    /** Idle references - waiting to be borrowed */
    065    private final LinkedBlockingDeque<PooledSoftReference<T>> idleReferences =
    066        new LinkedBlockingDeque<>();
    067
    068    /** All references - checked out or waiting to be borrowed. */
    069    private final ArrayList<PooledSoftReference<T>> allReferences =
    070        new ArrayList<>();
    071
    072    /**
    073     * Create a {@code SoftReferenceObjectPool} with the specified factory.
    074     *
    075     * @param factory object factory to use.
    076     */
    077    public SoftReferenceObjectPool(final PooledObjectFactory<T> factory) {
    078        this.factory = factory;
    079    }
    080
    081    /**
    082     * Borrows an object from the pool. If there are no idle instances available
    083     * in the pool, the configured factory's
    084     * {@link PooledObjectFactory#makeObject()} method is invoked to create a
    085     * new instance.
    086     * <p>
    087     * All instances are {@link PooledObjectFactory#activateObject(
    088     * org.apache.commons.pool2.PooledObject) activated}
    089     * and {@link PooledObjectFactory#validateObject(
    090     * org.apache.commons.pool2.PooledObject)
    091     * validated} before being returned by this method. If validation fails or
    092     * an exception occurs activating or validating an idle instance, the
    093     * failing instance is {@link PooledObjectFactory#destroyObject(
    094     * org.apache.commons.pool2.PooledObject)
    095     * destroyed} and another instance is retrieved from the pool, validated and
    096     * activated. This process continues until either the pool is empty or an
    097     * instance passes validation. If the pool is empty on activation or it does
    098     * not contain any valid instances, the factory's {@code makeObject}
    099     * method is used to create a new instance. If the created instance either
    100     * raises an exception on activation or fails validation,
    101     * {@code NoSuchElementException} is thrown. Exceptions thrown by
    102     * {@code MakeObject} are propagated to the caller; but other than
    103     * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions
    104     * generated by activation, validation or destroy methods are swallowed
    105     * silently.
    106     *
    107     * @throws NoSuchElementException
    108     *             if a valid object cannot be provided
    109     * @throws IllegalStateException
    110     *             if invoked on a {@link #close() closed} pool
    111     * @throws Exception
    112     *             if an exception occurs creating a new instance
    113     * @return a valid, activated object instance
    114     */
    115    @SuppressWarnings("null") // ref cannot be null
    116    @Override
    117    public synchronized T borrowObject() throws Exception {
    118        assertOpen();
    119        T obj = null;
    120        boolean newlyCreated = false;
    121        PooledSoftReference<T> ref = null;
    122        while (null == obj) {
    123            if (idleReferences.isEmpty()) {
    124                if (null == factory) {
    125                    throw new NoSuchElementException();
    126                }
    127                newlyCreated = true;
    128                obj = factory.makeObject().getObject();
    129                createCount++;
    130                // Do not register with the queue
    131                ref = new PooledSoftReference<>(new SoftReference<>(obj));
    132                allReferences.add(ref);
    133            } else {
    134                ref = idleReferences.pollFirst();
    135                obj = ref.getObject();
    136                // Clear the reference so it will not be queued, but replace with a
    137                // a new, non-registered reference so we can still track this object
    138                // in allReferences
    139                ref.getReference().clear();
    140                ref.setReference(new SoftReference<>(obj));
    141            }
    142            if (null != factory && null != obj) {
    143                try {
    144                    factory.activateObject(ref);
    145                    if (!factory.validateObject(ref)) {
    146                        throw new Exception("ValidateObject failed");
    147                    }
    148                } catch (final Throwable t) {
    149                    PoolUtils.checkRethrow(t);
    150                    try {
    151                        destroy(ref);
    152                    } catch (final Throwable t2) {
    153                        PoolUtils.checkRethrow(t2);
    154                        // Swallowed
    155                    } finally {
    156                        obj = null;
    157                    }
    158                    if (newlyCreated) {
    159                        throw new NoSuchElementException(
    160                                "Could not create a validated object, cause: " +
    161                                        t.getMessage());
    162                    }
    163                }
    164            }
    165        }
    166        numActive++;
    167        ref.allocate();
    168        return obj;
    169    }
    170
    171    /**
    172     * Returns an instance to the pool after successful validation and
    173     * passivation. The returning instance is destroyed if any of the following
    174     * are true:
    175     * <ul>
    176     * <li>the pool is closed</li>
    177     * <li>{@link PooledObjectFactory#validateObject(
    178     * org.apache.commons.pool2.PooledObject) validation} fails
    179     * </li>
    180     * <li>{@link PooledObjectFactory#passivateObject(
    181     * org.apache.commons.pool2.PooledObject) passivation}
    182     * throws an exception</li>
    183     * </ul>
    184     * Exceptions passivating or destroying instances are silently swallowed.
    185     * Exceptions validating instances are propagated to the client.
    186     *
    187     * @param obj
    188     *            instance to return to the pool
    189     * @throws IllegalArgumentException
    190     *            if obj is not currently part of this pool
    191     */
    192    @Override
    193    public synchronized void returnObject(final T obj) throws Exception {
    194        boolean success = !isClosed();
    195        final PooledSoftReference<T> ref = findReference(obj);
    196        if (ref == null) {
    197            throw new IllegalStateException(
    198                "Returned object not currently part of this pool");
    199        }
    200        if (factory != null) {
    201            if (!factory.validateObject(ref)) {
    202                success = false;
    203            } else {
    204                try {
    205                    factory.passivateObject(ref);
    206                } catch (final Exception e) {
    207                    success = false;
    208                }
    209            }
    210        }
    211
    212        final boolean shouldDestroy = !success;
    213        numActive--;
    214        if (success) {
    215
    216            // Deallocate and add to the idle instance pool
    217            ref.deallocate();
    218            idleReferences.add(ref);
    219        }
    220        notifyAll(); // numActive has changed
    221
    222        if (shouldDestroy && factory != null) {
    223            try {
    224                destroy(ref);
    225            } catch (final Exception e) {
    226                // ignored
    227            }
    228        }
    229    }
    230
    231    /**
    232     * {@inheritDoc}
    233     */
    234    @Override
    235    public synchronized void invalidateObject(final T obj) throws Exception {
    236        final PooledSoftReference<T> ref = findReference(obj);
    237        if (ref == null) {
    238            throw new IllegalStateException(
    239                "Object to invalidate is not currently part of this pool");
    240        }
    241        if (factory != null) {
    242            destroy(ref);
    243        }
    244        numActive--;
    245        notifyAll(); // numActive has changed
    246    }
    247
    248    /**
    249     * Creates an object, and places it into the pool. addObject() is useful for
    250     * "pre-loading" a pool with idle objects.
    251     * <p>
    252     * Before being added to the pool, the newly created instance is
    253     * {@link PooledObjectFactory#validateObject(
    254     * org.apache.commons.pool2.PooledObject) validated} and
    255     * {@link PooledObjectFactory#passivateObject(
    256     * org.apache.commons.pool2.PooledObject) passivated}. If
    257     * validation fails, the new instance is
    258     * {@link PooledObjectFactory#destroyObject(
    259     * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions
    260     * generated by the factory {@code makeObject} or
    261     * {@code passivate} are propagated to the caller. Exceptions
    262     * destroying instances are silently swallowed.
    263     *
    264     * @throws IllegalStateException
    265     *             if invoked on a {@link #close() closed} pool
    266     * @throws Exception
    267     *             when the {@link #getFactory() factory} has a problem creating
    268     *             or passivating an object.
    269     */
    270    @Override
    271    public synchronized void addObject() throws Exception {
    272        assertOpen();
    273        if (factory == null) {
    274            throw new IllegalStateException(
    275                    "Cannot add objects without a factory.");
    276        }
    277        final T obj = factory.makeObject().getObject();
    278        createCount++;
    279        // Create and register with the queue
    280        final PooledSoftReference<T> ref = new PooledSoftReference<>(
    281                new SoftReference<>(obj, refQueue));
    282        allReferences.add(ref);
    283
    284        boolean success = true;
    285        if (!factory.validateObject(ref)) {
    286            success = false;
    287        } else {
    288            factory.passivateObject(ref);
    289        }
    290
    291        final boolean shouldDestroy = !success;
    292        if (success) {
    293            idleReferences.add(ref);
    294            notifyAll(); // numActive has changed
    295        }
    296
    297        if (shouldDestroy) {
    298            try {
    299                destroy(ref);
    300            } catch (final Exception e) {
    301                // ignored
    302            }
    303        }
    304    }
    305
    306    /**
    307     * Returns an approximation not less than the of the number of idle
    308     * instances in the pool.
    309     *
    310     * @return estimated number of idle instances in the pool
    311     */
    312    @Override
    313    public synchronized int getNumIdle() {
    314        pruneClearedReferences();
    315        return idleReferences.size();
    316    }
    317
    318    /**
    319     * Returns the number of instances currently borrowed from this pool.
    320     *
    321     * @return the number of instances currently borrowed from this pool
    322     */
    323    @Override
    324    public synchronized int getNumActive() {
    325        return numActive;
    326    }
    327
    328    /**
    329     * Clears any objects sitting idle in the pool.
    330     */
    331    @Override
    332    public synchronized void clear() {
    333        if (null != factory) {
    334            final Iterator<PooledSoftReference<T>> iter = idleReferences.iterator();
    335            while (iter.hasNext()) {
    336                try {
    337                    final PooledSoftReference<T> ref = iter.next();
    338                    if (null != ref.getObject()) {
    339                        factory.destroyObject(ref);
    340                    }
    341                } catch (final Exception e) {
    342                    // ignore error, keep destroying the rest
    343                }
    344            }
    345        }
    346        idleReferences.clear();
    347        pruneClearedReferences();
    348    }
    349
    350    /**
    351     * Closes this pool, and frees any resources associated with it. Invokes
    352     * {@link #clear()} to destroy and remove instances in the pool.
    353     * <p>
    354     * Calling {@link #addObject} or {@link #borrowObject} after invoking this
    355     * method on a pool will cause them to throw an
    356     * {@link IllegalStateException}.
    357     */
    358    @Override
    359    public void close() {
    360        super.close();
    361        clear();
    362    }
    363
    364    /**
    365     * Returns the {@link PooledObjectFactory} used by this pool to create and
    366     * manage object instances.
    367     *
    368     * @return the factory
    369     */
    370    public synchronized PooledObjectFactory<T> getFactory() {
    371        return factory;
    372    }
    373
    374    /**
    375     * If any idle objects were garbage collected, remove their
    376     * {@link Reference} wrappers from the idle object pool.
    377     */
    378    private void pruneClearedReferences() {
    379        // Remove wrappers for enqueued references from idle and allReferences lists
    380        removeClearedReferences(idleReferences.iterator());
    381        removeClearedReferences(allReferences.iterator());
    382        while (refQueue.poll() != null) {
    383            // empty
    384        }
    385    }
    386
    387    /**
    388     * Finds the PooledSoftReference in allReferences that points to obj.
    389     *
    390     * @param obj returning object
    391     * @return PooledSoftReference wrapping a soft reference to obj
    392     */
    393    private PooledSoftReference<T> findReference(final T obj) {
    394        final Iterator<PooledSoftReference<T>> iterator = allReferences.iterator();
    395        while (iterator.hasNext()) {
    396            final PooledSoftReference<T> reference = iterator.next();
    397            if (reference.getObject() != null && reference.getObject().equals(obj)) {
    398                return reference;
    399            }
    400        }
    401        return null;
    402    }
    403
    404    /**
    405     * Destroys a {@code PooledSoftReference} and removes it from the idle and all
    406     * references pools.
    407     *
    408     * @param toDestroy PooledSoftReference to destroy
    409     *
    410     * @throws Exception If an error occurs while trying to destroy the object
    411     */
    412    private void destroy(final PooledSoftReference<T> toDestroy) throws Exception {
    413        toDestroy.invalidate();
    414        idleReferences.remove(toDestroy);
    415        allReferences.remove(toDestroy);
    416        try {
    417            factory.destroyObject(toDestroy);
    418        } finally {
    419            destroyCount++;
    420            toDestroy.getReference().clear();
    421        }
    422    }
    423
    424    /**
    425     * Clears cleared references from iterator's collection
    426     * @param iterator iterator over idle/allReferences
    427     */
    428    private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) {
    429        PooledSoftReference<T> ref;
    430        while (iterator.hasNext()) {
    431            ref = iterator.next();
    432            if (ref.getReference() == null || ref.getReference().isEnqueued()) {
    433                iterator.remove();
    434            }
    435        }
    436    }
    437
    438    @Override
    439    protected void toStringAppendFields(final StringBuilder builder) {
    440        super.toStringAppendFields(builder);
    441        builder.append(", factory=");
    442        builder.append(factory);
    443        builder.append(", refQueue=");
    444        builder.append(refQueue);
    445        builder.append(", numActive=");
    446        builder.append(numActive);
    447        builder.append(", destroyCount=");
    448        builder.append(destroyCount);
    449        builder.append(", createCount=");
    450        builder.append(createCount);
    451        builder.append(", idleReferences=");
    452        builder.append(idleReferences);
    453        builder.append(", allReferences=");
    454        builder.append(allReferences);
    455    }
    456}