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;
29import java.util.Map;
30import java.util.WeakHashMap;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.LinkedList;
34
35import java.io.PrintStream;
36import java.lang.ref.Reference;
37import java.lang.ref.ReferenceQueue;
38import javax.naming.NamingException;
39
40/**
41 * A map of pool ids to Connections.
42 * Key is an object that uniquely identifies a PooledConnection request
43 * (typically information needed to create the connection).
44 * The definitions of the key's equals() and hashCode() methods are
45 * vital to its unique identification in a Pool.
46 *
47 * Value is a ConnectionsRef, which is a reference to Connections,
48 * a list of equivalent connections.
49 *
50 * Supports methods that
51 * - retrieves (or creates as necessary) a connection from the pool
52 * - removes expired connections from the pool
53 *
54 * Connections cleanup:
55 * A WeakHashMap is used for mapping the pool ids and Connections.
56 * A SoftReference from the value to the key is kept to hold the map
57 * entry as long as possible. This allows the GC to remove Connections
58 * from the Pool under situations of VM running out of resources.
59 * To take an appropriate action of 'closing the connections' before the GC
60 * reclaims the ConnectionsRef objects, the ConnectionsRef objects are made
61 * weakly reachable through a list of weak references registered with
62 * a reference queue.
63 * Upon an entry gets removed from the WeakHashMap, the ConnectionsRef (value
64 * in the map) object is weakly reachable. When another sweep of
65 * clearing the weak references is made by the GC it puts the corresponding
66 * ConnectionsWeakRef object into the reference queue.
67 * The reference queue is monitored lazily for reclaimable Connections
68 * whenever a pooled connection is requested or a call to remove the expired
69 * connections is made. The monitoring is done regularly when idle connection
70 * timeout is set as the PoolCleaner removes expired connections periodically.
71 * As determined by experimentation, cleanup of resources using the
72 * ReferenceQueue mechanism is reliable and has more immediate effect than the
73 * finalizer approach.
74 *
75 * @author Rosanna Lee
76 */
77
78final public class Pool {
79
80    static final boolean debug = com.sun.jndi.ldap.LdapPoolManager.debug;
81
82    /*
83     * Used for connections cleanup
84     */
85    private static final ReferenceQueue<ConnectionsRef> queue =
86        new ReferenceQueue<>();
87    private static final Collection<Reference<ConnectionsRef>> weakRefs =
88        Collections.synchronizedList(new LinkedList<Reference<ConnectionsRef>>());
89
90    final private int maxSize;    // max num of identical conn per pool
91    final private int prefSize;   // preferred num of identical conn per pool
92    final private int initSize;   // initial number of identical conn to create
93    final private Map<Object, ConnectionsRef> map;
94
95    public Pool(int initSize, int prefSize, int maxSize) {
96        map = new WeakHashMap<>();
97        this.prefSize = prefSize;
98        this.maxSize = maxSize;
99        this.initSize = initSize;
100    }
101
102    /**
103     * Gets a pooled connection for id. The pooled connection might be
104     * newly created, as governed by the maxSize and prefSize settings.
105     * If a pooled connection is unavailable and cannot be created due
106     * to the maxSize constraint, this call blocks until the constraint
107     * is removed or until 'timeout' ms has elapsed.
108     *
109     * @param id identity of the connection to get
110     * @param timeout the number of milliseconds to wait before giving up
111     * @param factory the factory to use for creating the connection if
112     *          creation is necessary
113     * @return a pooled connection
114     * @throws NamingException the connection could not be created due to
115     *                          an error.
116     */
117    public PooledConnection getPooledConnection(Object id, long timeout,
118        PooledConnectionFactory factory) throws NamingException {
119
120        d("get(): ", id);
121        if (debug) {
122            synchronized (map) {
123                d("size: ", map.size());
124            }
125        }
126
127        expungeStaleConnections();
128
129        Connections conns;
130        synchronized (map) {
131            conns = getConnections(id);
132            if (conns == null) {
133                d("get(): creating new connections list for ", id);
134
135                // No connections for this id so create a new list
136                conns = new Connections(id, initSize, prefSize, maxSize,
137                    factory);
138                ConnectionsRef connsRef = new ConnectionsRef(conns);
139                map.put(id, connsRef);
140
141                // Create a weak reference to ConnectionsRef
142                Reference<ConnectionsRef> weakRef =
143                        new ConnectionsWeakRef(connsRef, queue);
144
145                // Keep the weak reference through the element of a linked list
146                weakRefs.add(weakRef);
147            }
148            d("get(): size after: ", map.size());
149        }
150
151        return conns.get(timeout, factory); // get one connection from list
152    }
153
154    private Connections getConnections(Object id) {
155        ConnectionsRef ref = map.get(id);
156        return (ref != null) ? ref.getConnections() : null;
157    }
158
159    /**
160     * Goes through the connections in this Pool and expires ones that
161     * have been idle before 'threshold'. An expired connection is closed
162     * and then removed from the pool (removePooledConnection() will eventually
163     * be called, and the list of pools itself removed if it becomes empty).
164     *
165     * @param threshold connections idle before 'threshold' should be closed
166     *          and removed.
167     */
168    public void expire(long threshold) {
169        Collection<ConnectionsRef> copy;
170        synchronized (map) {
171            copy = new ArrayList<>(map.values());
172        }
173
174        ArrayList<ConnectionsRef> removed = new ArrayList<>();
175        Connections conns;
176        for (ConnectionsRef ref : copy) {
177            conns = ref.getConnections();
178            if (conns.expire(threshold)) {
179                d("expire(): removing ", conns);
180                removed.add(ref);
181            }
182        }
183
184        synchronized (map) {
185            map.values().removeAll(removed);
186        }
187
188        expungeStaleConnections();
189    }
190
191    /*
192     * Closes the connections contained in the ConnectionsRef object that
193     * is going to be reclaimed by the GC. Called by getPooledConnection()
194     * and expire() methods of this class.
195     */
196    private static void expungeStaleConnections() {
197        ConnectionsWeakRef releaseRef = null;
198        while ((releaseRef = (ConnectionsWeakRef) queue.poll())
199                                        != null) {
200            Connections conns = releaseRef.getConnections();
201
202            if (debug) {
203                System.err.println(
204                        "weak reference cleanup: Closing Connections:" + conns);
205            }
206
207            // cleanup
208            conns.close();
209            weakRefs.remove(releaseRef);
210            releaseRef.clear();
211         }
212    }
213
214
215    public void showStats(PrintStream out) {
216        Object id;
217        Connections conns;
218
219        out.println("===== Pool start ======================");
220        out.println("maximum pool size: " + maxSize);
221        out.println("preferred pool size: " + prefSize);
222        out.println("initial pool size: " + initSize);
223
224        synchronized (map) {
225            out.println("current pool size: " + map.size());
226
227            for (Map.Entry<Object, ConnectionsRef> entry : map.entrySet()) {
228                id = entry.getKey();
229                conns = entry.getValue().getConnections();
230                out.println("   " + id + ":" + conns.getStats());
231            }
232        }
233
234        out.println("====== Pool end =====================");
235    }
236
237    public String toString() {
238        synchronized (map) {
239            return super.toString() + " " + map.toString();
240        }
241    }
242
243    private void d(String msg, int i) {
244        if (debug) {
245            System.err.println(this + "." + msg + i);
246        }
247    }
248
249    private void d(String msg, Object obj) {
250        if (debug) {
251            System.err.println(this + "." + msg + obj);
252        }
253    }
254}
255