aeb1fcd5ecc13c0b478c79d8d2fa2568a4718166
[appc.git] / appc-core / appc-common-bundle / src / main / java / org / onap / appc / pool / Pool.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * =============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * 
21  * ============LICENSE_END=========================================================
22  */
23
24
25
26 package org.onap.appc.pool;
27
28 import java.io.Closeable;
29 import java.util.ArrayDeque;
30 import java.util.ArrayList;
31 import java.util.Deque;
32 import java.util.List;
33 import java.util.ListIterator;
34 import java.util.Properties;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import java.util.concurrent.locks.Lock;
37 import java.util.concurrent.locks.ReadWriteLock;
38 import java.util.concurrent.locks.ReentrantReadWriteLock;
39
40 /**
41  * This class is used to manage a pool of things.
42  * <p>
43  * The class is parameterized so that the type of objects maintained in the pool is definable by some provided type.
44  * This type must implement the <code>Comparable</code> interface so that it can be managed in the pool.
45  * </p>
46  * 
47  * @param <T>
48  *            The type of element being pooled
49  */
50
51 public class Pool<T extends Closeable> {
52     private Deque<T> free;
53     private List<T> allocated;
54     private int minPool;
55     private int maxPool;
56     private Allocator<T> allocator;
57     private Destructor<T> destructor;
58     private ReadWriteLock lock;
59     private AtomicBoolean drained;
60     private Properties properties;
61
62     /**
63      * Create the pool
64      *
65      * @param minPool
66      *            The minimum size of the pool
67      * @param maxPool
68      *            The maximum size of the pool, set to zero (0) for unbounded
69      * @throws PoolSpecificationException
70      *             If the minimum size is less than 0, or if the max size is non-zero and less than the min size.
71      */
72     public Pool(int minPool, int maxPool) throws PoolSpecificationException {
73
74         if (minPool < 0) {
75             throw new PoolSpecificationException(String.format("The minimum pool size must be a "
76                 + "positive value or zero, %d is not valid.", minPool));
77         }
78         if (maxPool != 0 && maxPool < minPool) {
79             throw new PoolSpecificationException(String.format("The maximum pool size must be a "
80                 + "positive value greater than the minimum size, or zero. %d is not valid.", maxPool));
81         }
82
83         this.minPool = minPool;
84         this.maxPool = maxPool;
85
86         properties = new Properties();
87         free = new ArrayDeque<T>();
88         allocated = new ArrayList<T>();
89         lock = new ReentrantReadWriteLock();
90         drained = new AtomicBoolean(false);
91     }
92
93     /**
94      * Returns the amount of objects on the free collection
95      *
96      * @return The number of objects on the free collection
97      */
98     public int getFreeSize() {
99         Lock readLock = lock.readLock();
100         readLock.lock();
101         try {
102             return free.size();
103         } finally {
104             readLock.unlock();
105         }
106     }
107
108     /**
109      * Returns the value for a specified property of this pool, if defined.
110      * 
111      * @param key
112      *            The key of the desired property
113      * @return The value of the property, or null if not defined
114      */
115     public String getProperty(String key) {
116         return properties.getProperty(key);
117     }
118
119     /**
120      * Sets the value of the specified property or replaces it if it already exists
121      * 
122      * @param key
123      *            The key of the property to be set
124      * @param value
125      *            The value to set the property to
126      */
127     public void setProperty(String key, String value) {
128         properties.setProperty(key, value);
129     }
130
131     /**
132      * @return The properties object for the pool
133      */
134     public Properties getProperties() {
135         return properties;
136     }
137
138     /**
139      * Returns the number of objects that are currently allocated
140      *
141      * @return The allocate collection size
142      */
143     public int getAllocatedSize() {
144         Lock readLock = lock.readLock();
145         readLock.lock();
146         try {
147             return allocated.size();
148         } finally {
149             readLock.unlock();
150         }
151     }
152
153     /**
154      * @return the value of allocator
155      */
156     public Allocator<T> getAllocator() {
157         return allocator;
158     }
159
160     /**
161      * @param allocator
162      *            the value for allocator
163      */
164     public void setAllocator(Allocator<T> allocator) {
165         this.allocator = allocator;
166     }
167
168     /**
169      * @return the value of destructor
170      */
171     public Destructor<T> getDestructor() {
172         return destructor;
173     }
174
175     /**
176      * @return the value of minPool
177      */
178     public int getMinPool() {
179         return minPool;
180     }
181
182     /**
183      * @return the value of maxPool
184      */
185     public int getMaxPool() {
186         return maxPool;
187     }
188
189     /**
190      * @param destructor
191      *            the value for destructor
192      */
193     public void setDestructor(Destructor<T> destructor) {
194         this.destructor = destructor;
195     }
196
197     /**
198      * Drains the pool, releasing and destroying all pooled objects, even if they are currently allocated.
199      */
200     public void drain() {
201         if (drained.compareAndSet(false, true)) {
202             Lock writeLock = lock.writeLock();
203             writeLock.lock();
204             try {
205                 int size = getAllocatedSize();
206                 /*
207                  * We can't use the "release" method call here because we are modifying the list we are iterating
208                  */
209                 ListIterator<T> it = allocated.listIterator();
210                 while (it.hasNext()) {
211                     T obj = it.next();
212                     it.remove();
213                     free.addFirst(obj);
214                 }
215                 size = getFreeSize();
216                 trim(size);
217             } finally {
218                 writeLock.unlock();
219             }
220         }
221     }
222
223     /**
224      * Returns an indication if the pool has been drained
225      *
226      * @return True indicates that the pool has been drained. Once a pool has been drained, it can no longer be used.
227      */
228     public boolean isDrained() {
229         return drained.get();
230     }
231
232     /**
233      * Reserves an object of type T from the pool for the caller and returns it
234      *
235      * @return The object of type T to be used by the caller
236      * @throws PoolExtensionException
237      *             If the pool cannot be extended
238      * @throws PoolDrainedException
239      *             If the caller is trying to reserve an element from a drained pool
240      */
241     @SuppressWarnings("unchecked")
242     public T reserve() throws PoolExtensionException, PoolDrainedException {
243         if (isDrained()) {
244             throw new PoolDrainedException("The pool has been drained and cannot be used.");
245         }
246
247         T obj = null;
248         Lock writeLock = lock.writeLock();
249         writeLock.lock();
250         try {
251             int freeSize = getFreeSize();
252             int allocatedSize = getAllocatedSize();
253
254             if (freeSize == 0) {
255                 if (allocatedSize == 0) {
256                     extend(minPool == 0 ? 1 : minPool);
257                 } else if (allocatedSize >= maxPool && maxPool > 0) {
258                     throw new PoolExtensionException(String.format("Unable to add "
259                         + "more elements, pool is at maximum size of %d", maxPool));
260                 } else {
261                     extend(1);
262                 }
263             }
264
265             obj = free.removeFirst();
266             allocated.add(obj);
267         } finally {
268             writeLock.unlock();
269         }
270
271         /*
272          * Now that we have the real object, lets wrap it in a dynamic proxy so that we can intercept the close call and
273          * just return the context to the free pool. obj.getClass().getInterfaces(). We need to find ALL interfaces that
274          * the object (and all superclasses) implement and have the proxy implement them too
275          */
276         Class<?> cls = obj.getClass();
277         Class<?>[] array;
278         List<Class<?>> interfaces = new ArrayList<Class<?>>();
279         while (!cls.equals(Object.class)) {
280             array = cls.getInterfaces();
281             for (Class<?> item : array) {
282                 if (!interfaces.contains(item)) {
283                     interfaces.add(item);
284                 }
285             }
286             cls = cls.getSuperclass();
287         }
288         array = new Class<?>[interfaces.size()];
289         array = interfaces.toArray(array);
290         return CachedElement.newInstance(this, obj, array);
291     }
292
293     /**
294      * releases the allocated object back to the free pool to be used by another request.
295      *
296      * @param obj
297      *            The object to be returned to the pool
298      * @throws PoolDrainedException
299      *             If the caller is trying to release an element to a drained pool
300      */
301     public void release(T obj) throws PoolDrainedException {
302         if (isDrained()) {
303             throw new PoolDrainedException("The pool has been drained and cannot be used.");
304         }
305         Lock writeLock = lock.writeLock();
306         writeLock.lock();
307         try {
308             if (allocated.remove(obj)) {
309                 free.addFirst(obj);
310             }
311         } finally {
312             writeLock.unlock();
313         }
314     }
315
316     /**
317      * Extend the free pool by some number of elements
318      *
319      * @param count
320      *            The number of elements to add to the pool
321      * @throws PoolExtensionException
322      *             if the pool cannot be extended because no allocator has been specified.
323      */
324     private void extend(int count) throws PoolExtensionException {
325         if (allocator == null) {
326             throw new PoolExtensionException(String.format("Unable to extend pool "
327                 + "because no allocator has been specified"));
328         }
329         Lock writeLock = lock.writeLock();
330         writeLock.lock();
331         try {
332             for (int index = 0; index < count; index++) {
333                 T obj = allocator.allocate(this);
334                 if (obj == null) {
335                     throw new PoolExtensionException(
336                         "The allocator failed to allocate a new context to extend the pool.");
337                 }
338                 free.push(obj);
339             }
340         } finally {
341             writeLock.unlock();
342         }
343     }
344
345     /**
346      * Used to trim the free collection by some specified number of elements, or the free element count, whichever is
347      * less. The elements are removed from the end of the free element deque, thus trimming the oldest elements first.
348      *
349      * @param count
350      *            The number of elements to trim
351      */
352     private void trim(int count) {
353         Lock writeLock = lock.writeLock();
354         writeLock.lock();
355         try {
356             int trimCount = count;
357             if (getFreeSize() < count) {
358                 trimCount = getFreeSize();
359             }
360             for (int i = 0; i < trimCount; i++) {
361                 T obj = free.removeLast();
362                 if (destructor != null) {
363                     destructor.destroy(obj, this);
364                 }
365             }
366         } finally {
367             writeLock.unlock();
368         }
369     }
370 }