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</code>. 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</code> 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</code>
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</code> is thrown. Exceptions thrown by
102     * <code>MakeObject</code> are propagated to the caller; but other than
103     * <code>ThreadDeath</code> or <code>VirtualMachineError</code>, 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</code> or
261     * <code>passivate</code> 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}