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