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 
  26 package com.sun.jndi.ldap.pool;
  27 
  28 import java.util.ArrayList; // JDK 1.2
  29 import java.util.List;
  30 
  31 import java.lang.ref.Reference;
  32 import java.lang.ref.SoftReference;
  33 
  34 import javax.naming.NamingException;
  35 import javax.naming.InterruptedNamingException;
  36 import 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
  65 final 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 }