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; // @GuardedBy("this")
056
057    /** Total number of instances that have been destroyed */
058    private long destroyCount; // @GuardedBy("this")
059
060
061    /** Total number of instances that have been created */
062    private long createCount; // @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     * Creates an object, and places it into the pool. addObject() is useful for
083     * "pre-loading" a pool with idle objects.
084     * <p>
085     * Before being added to the pool, the newly created instance is
086     * {@link PooledObjectFactory#validateObject(
087     * org.apache.commons.pool2.PooledObject) validated} and
088     * {@link PooledObjectFactory#passivateObject(
089     * org.apache.commons.pool2.PooledObject) passivated}. If
090     * validation fails, the new instance is
091     * {@link PooledObjectFactory#destroyObject(
092     * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions
093     * generated by the factory {@code makeObject} or
094     * {@code passivate} are propagated to the caller. Exceptions
095     * destroying instances are silently swallowed.
096     *
097     * @throws IllegalStateException
098     *             if invoked on a {@link #close() closed} pool
099     * @throws Exception
100     *             when the {@link #getFactory() factory} has a problem creating
101     *             or passivating an object.
102     */
103    @Override
104    public synchronized void addObject() throws Exception {
105        assertOpen();
106        if (factory == null) {
107            throw new IllegalStateException(
108                    "Cannot add objects without a factory.");
109        }
110        final T obj = factory.makeObject().getObject();
111        createCount++;
112        // Create and register with the queue
113        final PooledSoftReference<T> ref = new PooledSoftReference<>(
114                new SoftReference<>(obj, refQueue));
115        allReferences.add(ref);
116
117        boolean success = true;
118        if (!factory.validateObject(ref)) {
119            success = false;
120        } else {
121            factory.passivateObject(ref);
122        }
123
124        final boolean shouldDestroy = !success;
125        if (success) {
126            idleReferences.add(ref);
127            notifyAll(); // numActive has changed
128        }
129
130        if (shouldDestroy) {
131            try {
132                destroy(ref);
133            } catch (final Exception e) {
134                // ignored
135            }
136        }
137    }
138
139    /**
140     * Borrows an object from the pool. If there are no idle instances available
141     * in the pool, the configured factory's
142     * {@link PooledObjectFactory#makeObject()} method is invoked to create a
143     * new instance.
144     * <p>
145     * All instances are {@link PooledObjectFactory#activateObject(
146     * org.apache.commons.pool2.PooledObject) activated}
147     * and {@link PooledObjectFactory#validateObject(
148     * org.apache.commons.pool2.PooledObject)
149     * validated} before being returned by this method. If validation fails or
150     * an exception occurs activating or validating an idle instance, the
151     * failing instance is {@link PooledObjectFactory#destroyObject(
152     * org.apache.commons.pool2.PooledObject)
153     * destroyed} and another instance is retrieved from the pool, validated and
154     * activated. This process continues until either the pool is empty or an
155     * instance passes validation. If the pool is empty on activation or it does
156     * not contain any valid instances, the factory's {@code makeObject}
157     * method is used to create a new instance. If the created instance either
158     * raises an exception on activation or fails validation,
159     * {@code NoSuchElementException} is thrown. Exceptions thrown by
160     * {@code MakeObject} are propagated to the caller; but other than
161     * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions
162     * generated by activation, validation or destroy methods are swallowed
163     * silently.
164     *
165     * @throws NoSuchElementException
166     *             if a valid object cannot be provided
167     * @throws IllegalStateException
168     *             if invoked on a {@link #close() closed} pool
169     * @throws Exception
170     *             if an exception occurs creating a new instance
171     * @return a valid, activated object instance
172     */
173    @SuppressWarnings("null") // ref cannot be null
174    @Override
175    public synchronized T borrowObject() throws Exception {
176        assertOpen();
177        T obj = null;
178        boolean newlyCreated = false;
179        PooledSoftReference<T> ref = null;
180        while (null == obj) {
181            if (idleReferences.isEmpty()) {
182                if (null == factory) {
183                    throw new NoSuchElementException();
184                }
185                newlyCreated = true;
186                obj = factory.makeObject().getObject();
187                createCount++;
188                // Do not register with the queue
189                ref = new PooledSoftReference<>(new SoftReference<>(obj));
190                allReferences.add(ref);
191            } else {
192                ref = idleReferences.pollFirst();
193                obj = ref.getObject();
194                // Clear the reference so it will not be queued, but replace with a
195                // a new, non-registered reference so we can still track this object
196                // in allReferences
197                ref.getReference().clear();
198                ref.setReference(new SoftReference<>(obj));
199            }
200            if (null != factory && null != obj) {
201                try {
202                    factory.activateObject(ref);
203                    if (!factory.validateObject(ref)) {
204                        throw new Exception("ValidateObject failed");
205                    }
206                } catch (final Throwable t) {
207                    PoolUtils.checkRethrow(t);
208                    try {
209                        destroy(ref);
210                    } catch (final Throwable t2) {
211                        PoolUtils.checkRethrow(t2);
212                        // Swallowed
213                    } finally {
214                        obj = null;
215                    }
216                    if (newlyCreated) {
217                        throw new NoSuchElementException(
218                                "Could not create a validated object, cause: " +
219                                        t.getMessage());
220                    }
221                }
222            }
223        }
224        numActive++;
225        ref.allocate();
226        return obj;
227    }
228
229    /**
230     * Clears any objects sitting idle in the pool.
231     */
232    @Override
233    public synchronized void clear() {
234        if (null != factory) {
235            final Iterator<PooledSoftReference<T>> iter = idleReferences.iterator();
236            while (iter.hasNext()) {
237                try {
238                    final PooledSoftReference<T> ref = iter.next();
239                    if (null != ref.getObject()) {
240                        factory.destroyObject(ref);
241                    }
242                } catch (final Exception e) {
243                    // ignore error, keep destroying the rest
244                }
245            }
246        }
247        idleReferences.clear();
248        pruneClearedReferences();
249    }
250
251    /**
252     * Closes this pool, and frees any resources associated with it. Invokes
253     * {@link #clear()} to destroy and remove instances in the pool.
254     * <p>
255     * Calling {@link #addObject} or {@link #borrowObject} after invoking this
256     * method on a pool will cause them to throw an
257     * {@link IllegalStateException}.
258     */
259    @Override
260    public void close() {
261        super.close();
262        clear();
263    }
264
265    /**
266     * Destroys a {@code PooledSoftReference} and removes it from the idle and all
267     * references pools.
268     *
269     * @param toDestroy PooledSoftReference to destroy
270     *
271     * @throws Exception If an error occurs while trying to destroy the object
272     */
273    private void destroy(final PooledSoftReference<T> toDestroy) throws Exception {
274        toDestroy.invalidate();
275        idleReferences.remove(toDestroy);
276        allReferences.remove(toDestroy);
277        try {
278            factory.destroyObject(toDestroy);
279        } finally {
280            destroyCount++;
281            toDestroy.getReference().clear();
282        }
283    }
284
285    /**
286     * Finds the PooledSoftReference in allReferences that points to obj.
287     *
288     * @param obj returning object
289     * @return PooledSoftReference wrapping a soft reference to obj
290     */
291    private PooledSoftReference<T> findReference(final T obj) {
292        final Iterator<PooledSoftReference<T>> iterator = allReferences.iterator();
293        while (iterator.hasNext()) {
294            final PooledSoftReference<T> reference = iterator.next();
295            if (reference.getObject() != null && reference.getObject().equals(obj)) {
296                return reference;
297            }
298        }
299        return null;
300    }
301
302    /**
303     * Gets the {@link PooledObjectFactory} used by this pool to create and
304     * manage object instances.
305     *
306     * @return the factory
307     */
308    public synchronized PooledObjectFactory<T> getFactory() {
309        return factory;
310    }
311
312    /**
313     * Gets the number of instances currently borrowed from this pool.
314     *
315     * @return the number of instances currently borrowed from this pool
316     */
317    @Override
318    public synchronized int getNumActive() {
319        return numActive;
320    }
321
322    /**
323     * Gets an approximation not less than the of the number of idle
324     * instances in the pool.
325     *
326     * @return estimated number of idle instances in the pool
327     */
328    @Override
329    public synchronized int getNumIdle() {
330        pruneClearedReferences();
331        return idleReferences.size();
332    }
333
334    /**
335     * {@inheritDoc}
336     */
337    @Override
338    public synchronized void invalidateObject(final T obj) throws Exception {
339        final PooledSoftReference<T> ref = findReference(obj);
340        if (ref == null) {
341            throw new IllegalStateException(
342                "Object to invalidate is not currently part of this pool");
343        }
344        if (factory != null) {
345            destroy(ref);
346        }
347        numActive--;
348        notifyAll(); // numActive has changed
349    }
350
351    /**
352     * If any idle objects were garbage collected, remove their
353     * {@link Reference} wrappers from the idle object pool.
354     */
355    private void pruneClearedReferences() {
356        // Remove wrappers for enqueued references from idle and allReferences lists
357        removeClearedReferences(idleReferences.iterator());
358        removeClearedReferences(allReferences.iterator());
359        while (refQueue.poll() != null) {
360            // empty
361        }
362    }
363
364    /**
365     * Clears cleared references from iterator's collection
366     * @param iterator iterator over idle/allReferences
367     */
368    private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) {
369        PooledSoftReference<T> ref;
370        while (iterator.hasNext()) {
371            ref = iterator.next();
372            if (ref.getReference() == null || ref.getReference().isEnqueued()) {
373                iterator.remove();
374            }
375        }
376    }
377
378    /**
379     * Returns an instance to the pool after successful validation and
380     * passivation. The returning instance is destroyed if any of the following
381     * are true:
382     * <ul>
383     * <li>the pool is closed</li>
384     * <li>{@link PooledObjectFactory#validateObject(
385     * org.apache.commons.pool2.PooledObject) validation} fails
386     * </li>
387     * <li>{@link PooledObjectFactory#passivateObject(
388     * org.apache.commons.pool2.PooledObject) passivation}
389     * throws an exception</li>
390     * </ul>
391     * Exceptions passivating or destroying instances are silently swallowed.
392     * Exceptions validating instances are propagated to the client.
393     *
394     * @param obj
395     *            instance to return to the pool
396     * @throws IllegalArgumentException
397     *            if obj is not currently part of this pool
398     */
399    @Override
400    public synchronized void returnObject(final T obj) throws Exception {
401        boolean success = !isClosed();
402        final PooledSoftReference<T> ref = findReference(obj);
403        if (ref == null) {
404            throw new IllegalStateException(
405                "Returned object not currently part of this pool");
406        }
407        if (factory != null) {
408            if (!factory.validateObject(ref)) {
409                success = false;
410            } else {
411                try {
412                    factory.passivateObject(ref);
413                } catch (final Exception e) {
414                    success = false;
415                }
416            }
417        }
418
419        final boolean shouldDestroy = !success;
420        numActive--;
421        if (success) {
422
423            // Deallocate and add to the idle instance pool
424            ref.deallocate();
425            idleReferences.add(ref);
426        }
427        notifyAll(); // numActive has changed
428
429        if (shouldDestroy && factory != null) {
430            try {
431                destroy(ref);
432            } catch (final Exception e) {
433                // ignored
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}