1/*
2 * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.jndi.ldap.pool;
27
28import java.util.ArrayList; // JDK 1.2
29import java.util.List;
30
31import java.lang.ref.Reference;
32import java.lang.ref.SoftReference;
33
34import javax.naming.NamingException;
35import javax.naming.InterruptedNamingException;
36import javax.naming.CommunicationException;
37
38/**
39 * Represents a list of PooledConnections (actually, ConnectionDescs) with the
40 * same pool id.
41 * The list starts out with an initial number of connections.
42 * Additional PooledConnections are created lazily upon demand.
43 * The list has a maximum size. When the number of connections
44 * reaches the maximum size, a request for a PooledConnection blocks until
45 * a connection is returned to the list. A maximum size of zero means that
46 * there is no maximum: connection creation will be attempted when
47 * no idle connection is available.
48 *
49 * The list may also have a preferred size. If the current list size
50 * is less than the preferred size, a request for a connection will result in
51 * a PooledConnection being created (even if an idle connection is available).
52 * If the current list size is greater than the preferred size,
53 * a connection being returned to the list will be closed and removed from
54 * the list. A preferred size of zero means that there is no preferred size:
55 * connections are created only when no idle connection is available and
56 * a connection being returned to the list is not closed. Regardless of the
57 * preferred size, connection creation always observes the maximum size:
58 * a connection won't be created if the list size is at or exceeds the
59 * maximum size.
60 *
61 * @author Rosanna Lee
62 */
63
64// Package private: accessed only by Pool
65final class Connections implements PoolCallback {
66    private static final boolean debug = Pool.debug;
67    private static final boolean trace =
68        com.sun.jndi.ldap.LdapPoolManager.trace;
69    private static final int DEFAULT_SIZE = 10;
70
71    final private int maxSize;
72    final private int prefSize;
73    final private List<ConnectionDesc> conns;
74
75    private boolean closed = false;   // Closed for business
76    private Reference<Object> ref; // maintains reference to id to prevent premature GC
77
78    /**
79     * @param id the identity (connection request) of the connections in the list
80     * @param initSize the number of connections to create initially
81     * @param prefSize the preferred size of the pool. The pool will try
82     * to maintain a pool of this size by creating and closing connections
83     * as needed.
84     * @param maxSize the maximum size of the pool. The pool will not exceed
85     * this size. If the pool is at this size, a request for a connection
86     * will block until an idle connection is released to the pool or
87     * when one is removed.
88     * @param factory The factory responsible for creating a connection
89     */
90    Connections(Object id, int initSize, int prefSize, int maxSize,
91        PooledConnectionFactory factory) throws NamingException {
92
93        this.maxSize = maxSize;
94        if (maxSize > 0) {
95            // prefSize and initSize cannot exceed specified maxSize
96            this.prefSize = Math.min(prefSize, maxSize);
97            initSize = Math.min(initSize, maxSize);
98        } else {
99            this.prefSize = prefSize;
100        }
101        conns = new ArrayList<>(maxSize > 0 ? maxSize : DEFAULT_SIZE);
102
103        // Maintain soft ref to id so that this Connections' entry in
104        // Pool doesn't get GC'ed prematurely
105        ref = new SoftReference<>(id);
106
107        d("init size=", initSize);
108        d("max size=", maxSize);
109        d("preferred size=", prefSize);
110
111        // Create initial connections
112        PooledConnection conn;
113        for (int i = 0; i < initSize; i++) {
114            conn = factory.createPooledConnection(this);
115            td("Create ", conn ,factory);
116            conns.add(new ConnectionDesc(conn)); // Add new idle conn to pool
117        }
118    }
119
120    /**
121     * Retrieves a PooledConnection from this list of connections.
122     * Use an existing one if one is idle, or create one if the list's
123     * max size hasn't been reached. If max size has been reached, wait
124     * for a PooledConnection to be returned, or one to be removed (thus
125     * not reaching the max size any longer).
126     *
127     * @param timeout if > 0, msec to wait until connection is available
128     * @param factory creates the PooledConnection if one needs to be created
129     *
130     * @return A non-null PooledConnection
131     * @throws NamingException PooledConnection cannot be created, because this
132     * thread was interrupted while it waited for an available connection,
133     * or if it timed out while waiting, or the creation of a connection
134     * resulted in an error.
135     */
136    synchronized PooledConnection get(long timeout,
137        PooledConnectionFactory factory) throws NamingException {
138        PooledConnection conn;
139        long start = (timeout > 0 ? System.currentTimeMillis() : 0);
140        long waittime = timeout;
141
142        d("get(): before");
143        while ((conn = getOrCreateConnection(factory)) == null) {
144            if (timeout > 0 && waittime <= 0) {
145                throw new CommunicationException(
146                    "Timeout exceeded while waiting for a connection: " +
147                    timeout + "ms");
148            }
149            try {
150                d("get(): waiting");
151                if (waittime > 0) {
152                    wait(waittime);  // Wait until one is released or removed
153                } else {
154                    wait();
155                }
156            } catch (InterruptedException e) {
157                throw new InterruptedNamingException(
158                    "Interrupted while waiting for a connection");
159            }
160            // Check whether we timed out
161            if (timeout > 0) {
162                long now = System.currentTimeMillis();
163                waittime = timeout - (now - start);
164            }
165        }
166
167        d("get(): after");
168        return conn;
169    }
170
171    /**
172     * Retrieves an idle connection from this list if one is available.
173     * If none is available, create a new one if maxSize hasn't been reached.
174     * If maxSize has been reached, return null.
175     * Always called from a synchronized method.
176     */
177    private PooledConnection getOrCreateConnection(
178        PooledConnectionFactory factory) throws NamingException {
179
180        int size = conns.size(); // Current number of idle/nonidle conns
181        PooledConnection conn = null;
182
183        if (prefSize <= 0 || size >= prefSize) {
184            // If no prefSize specified, or list size already meets or
185            // exceeds prefSize, then first look for an idle connection
186            ConnectionDesc entry;
187            for (int i = 0; i < size; i++) {
188                entry = conns.get(i);
189                if ((conn = entry.tryUse()) != null) {
190                    d("get(): use ", conn);
191                    td("Use ", conn);
192                    return conn;
193                }
194            }
195        }
196
197        // Check if list size already at maxSize specified
198        if (maxSize > 0 && size >= maxSize) {
199            return null;   // List size is at limit; cannot create any more
200        }
201
202        conn = factory.createPooledConnection(this);
203        td("Create and use ", conn, factory);
204        conns.add(new ConnectionDesc(conn, true)); // Add new conn to pool
205
206        return conn;
207    }
208
209    /**
210     * Releases connection back into list.
211     * If the list size is below prefSize, the connection may be reused.
212     * If the list size exceeds prefSize, then the connection is closed
213     * and removed from the list.
214     *
215     * public because implemented as part of PoolCallback.
216     */
217    public synchronized boolean releasePooledConnection(PooledConnection conn) {
218        ConnectionDesc entry;
219        int loc = conns.indexOf(entry=new ConnectionDesc(conn));
220
221        d("release(): ", conn);
222
223        if (loc >= 0) {
224            // Found entry
225
226            if (closed || (prefSize > 0 && conns.size() > prefSize)) {
227                // If list size exceeds prefSize, close connection
228
229                d("release(): closing ", conn);
230                td("Close ", conn);
231
232                // size must be >= 2 so don't worry about empty list
233                conns.remove(entry);
234                conn.closeConnection();
235
236            } else {
237                d("release(): release ", conn);
238                td("Release ", conn);
239
240                // Get ConnectionDesc from list to get correct state info
241                entry = conns.get(loc);
242                // Return connection to list, ready for reuse
243                entry.release();
244            }
245            notifyAll();
246            d("release(): notify");
247            return true;
248        } else {
249            return false;
250        }
251    }
252
253    /**
254     * Removes PooledConnection from list of connections.
255     * The closing of the connection is separate from this method.
256     * This method is called usually when the caller encounters an error
257     * when using the connection and wants it removed from the pool.
258     *
259     * @return true if conn removed; false if it was not in pool
260     *
261     * public because implemented as part of PoolCallback.
262     */
263    public synchronized boolean removePooledConnection(PooledConnection conn) {
264        if (conns.remove(new ConnectionDesc(conn))) {
265            d("remove(): ", conn);
266
267            notifyAll();
268
269            d("remove(): notify");
270            td("Remove ", conn);
271
272            if (conns.isEmpty()) {
273                // Remove softref to make pool entry eligible for GC.
274                // Once ref has been removed, it cannot be reinstated.
275                ref = null;
276            }
277
278            return true;
279        } else {
280            d("remove(): not found ", conn);
281            return false;
282        }
283    }
284
285    /**
286     * Goes through all entries in list, removes and closes ones that have been
287     * idle before threshold.
288     *
289     * @param threshold an entry idle since this time has expired.
290     * @return true if no more connections in list
291     */
292    boolean expire(long threshold) {
293        List<ConnectionDesc> clonedConns;
294        synchronized(this) {
295            clonedConns = new ArrayList<>(conns);
296        }
297        List<ConnectionDesc> expired = new ArrayList<>();
298
299        for (ConnectionDesc entry : clonedConns) {
300            d("expire(): ", entry);
301            if (entry.expire(threshold)) {
302                expired.add(entry);
303                td("expire(): Expired ", entry);
304            }
305        }
306
307        synchronized (this) {
308            conns.removeAll(expired);
309            // Don't need to call notify() because we're
310            // removing only idle connections. If there were
311            // idle connections, then there should be no waiters.
312            return conns.isEmpty();  // whether whole list has 'expired'
313        }
314    }
315
316    /**
317     * Called when this instance of Connections has been removed from Pool.
318     * This means that no one can get any pooled connections from this
319     * Connections any longer. Expire all idle connections as of 'now'
320     * and leave indicator so that any in-use connections will be closed upon
321     * their return.
322     */
323    synchronized void close() {
324        expire(System.currentTimeMillis());     // Expire idle connections
325        closed = true;   // Close in-use connections when they are returned
326    }
327
328    String getStats() {
329        int idle = 0;
330        int busy = 0;
331        int expired = 0;
332        long use = 0;
333        int len;
334
335        synchronized (this) {
336            len = conns.size();
337
338            ConnectionDesc entry;
339            for (int i = 0; i < len; i++) {
340                entry = conns.get(i);
341                use += entry.getUseCount();
342                switch (entry.getState()) {
343                case ConnectionDesc.BUSY:
344                    ++busy;
345                    break;
346                case ConnectionDesc.IDLE:
347                    ++idle;
348                    break;
349                case ConnectionDesc.EXPIRED:
350                    ++expired;
351                }
352            }
353        }
354        return "size=" + len + "; use=" + use + "; busy=" + busy
355            + "; idle=" + idle + "; expired=" + expired;
356    }
357
358    private void d(String msg, Object o1) {
359        if (debug) {
360            d(msg + o1);
361        }
362    }
363
364    private void d(String msg, int i) {
365        if (debug) {
366            d(msg + i);
367        }
368    }
369
370    private void d(String msg) {
371        if (debug) {
372            System.err.println(this + "." + msg + "; size: " + conns.size());
373        }
374    }
375
376    private void td(String msg, Object o1, Object o2) {
377        if (trace) { // redo test to avoid object creation
378            td(msg + o1 + "[" + o2 + "]");
379        }
380    }
381    private void td(String msg, Object o1) {
382        if (trace) { // redo test to avoid object creation
383            td(msg + o1);
384        }
385    }
386    private void td(String msg) {
387        if (trace) {
388            System.err.println(msg);
389        }
390    }
391}
392