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