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