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