1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java $
3 * $Revision: 677240 $
4 * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $
5 *
6 * ====================================================================
7 *
8 *  Licensed to the Apache Software Foundation (ASF) under one or more
9 *  contributor license agreements.  See the NOTICE file distributed with
10 *  this work for additional information regarding copyright ownership.
11 *  The ASF licenses this file to You under the Apache License, Version 2.0
12 *  (the "License"); you may not use this file except in compliance with
13 *  the License.  You may obtain a copy of the License at
14 *
15 *      http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 * ====================================================================
23 *
24 * This software consists of voluntary contributions made by many
25 * individuals on behalf of the Apache Software Foundation.  For more
26 * information on the Apache Software Foundation, please see
27 * <http://www.apache.org/>.
28 *
29 */
30
31package org.apache.http.impl.conn.tsccm;
32
33import java.io.IOException;
34import java.util.ListIterator;
35import java.util.Queue;
36import java.util.LinkedList;
37
38import org.apache.commons.logging.Log;
39import org.apache.commons.logging.LogFactory;
40import org.apache.http.conn.OperatedClientConnection;
41import org.apache.http.conn.routing.HttpRoute;
42import org.apache.http.util.LangUtils;
43
44
45/**
46 * A connection sub-pool for a specific route, used by {@link ConnPoolByRoute}.
47 * The methods in this class are unsynchronized. It is expected that the
48 * containing pool takes care of synchronization.
49 */
50public class RouteSpecificPool {
51
52    private final Log log = LogFactory.getLog(getClass());
53
54    /** The route this pool is for. */
55    protected final HttpRoute route;
56
57    /** the maximum number of entries allowed for this pool */
58    protected final int maxEntries;
59
60    /**
61     * The list of free entries.
62     * This list is managed LIFO, to increase idle times and
63     * allow for closing connections that are not really needed.
64     */
65    protected final LinkedList<BasicPoolEntry> freeEntries;
66
67    /** The list of threads waiting for this pool. */
68    protected final Queue<WaitingThread> waitingThreads;
69
70    /** The number of created entries. */
71    protected int numEntries;
72
73
74    /**
75     * Creates a new route-specific pool.
76     *
77     * @param route the route for which to pool
78     * @param maxEntries the maximum number of entries allowed for this pool
79     */
80    public RouteSpecificPool(HttpRoute route, int maxEntries) {
81        this.route = route;
82        this.maxEntries = maxEntries;
83        this.freeEntries = new LinkedList<BasicPoolEntry>();
84        this.waitingThreads = new LinkedList<WaitingThread>();
85        this.numEntries = 0;
86    }
87
88
89    /**
90     * Obtains the route for which this pool is specific.
91     *
92     * @return  the route
93     */
94    public final HttpRoute getRoute() {
95        return route;
96    }
97
98
99    /**
100     * Obtains the maximum number of entries allowed for this pool.
101     *
102     * @return  the max entry number
103     */
104    public final int getMaxEntries() {
105        return maxEntries;
106    }
107
108
109    /**
110     * Indicates whether this pool is unused.
111     * A pool is unused if there is neither an entry nor a waiting thread.
112     * All entries count, not only the free but also the allocated ones.
113     *
114     * @return  <code>true</code> if this pool is unused,
115     *          <code>false</code> otherwise
116     */
117    public boolean isUnused() {
118        return (numEntries < 1) && waitingThreads.isEmpty();
119    }
120
121
122    /**
123     * Return remaining capacity of this pool
124     *
125     * @return capacity
126     */
127    public int getCapacity() {
128        return maxEntries - numEntries;
129    }
130
131
132    /**
133     * Obtains the number of entries.
134     * This includes not only the free entries, but also those that
135     * have been created and are currently issued to an application.
136     *
137     * @return  the number of entries for the route of this pool
138     */
139    public final int getEntryCount() {
140        return numEntries;
141    }
142
143
144    /**
145     * Obtains a free entry from this pool, if one is available.
146     *
147     * @return an available pool entry, or <code>null</code> if there is none
148     */
149    public BasicPoolEntry allocEntry(final Object state) {
150        if (!freeEntries.isEmpty()) {
151            ListIterator<BasicPoolEntry> it = freeEntries.listIterator(freeEntries.size());
152            while (it.hasPrevious()) {
153                BasicPoolEntry entry = it.previous();
154                if (LangUtils.equals(state, entry.getState())) {
155                    it.remove();
156                    return entry;
157                }
158            }
159        }
160        if (!freeEntries.isEmpty()) {
161            BasicPoolEntry entry = freeEntries.remove();
162            entry.setState(null);
163            OperatedClientConnection conn = entry.getConnection();
164            try {
165                conn.close();
166            } catch (IOException ex) {
167                log.debug("I/O error closing connection", ex);
168            }
169            return entry;
170        }
171        return null;
172    }
173
174
175    /**
176     * Returns an allocated entry to this pool.
177     *
178     * @param entry     the entry obtained from {@link #allocEntry allocEntry}
179     *                  or presented to {@link #createdEntry createdEntry}
180     */
181    public void freeEntry(BasicPoolEntry entry) {
182
183        if (numEntries < 1) {
184            throw new IllegalStateException
185                ("No entry created for this pool. " + route);
186        }
187        if (numEntries <= freeEntries.size()) {
188            throw new IllegalStateException
189                ("No entry allocated from this pool. " + route);
190        }
191        freeEntries.add(entry);
192    }
193
194
195    /**
196     * Indicates creation of an entry for this pool.
197     * The entry will <i>not</i> be added to the list of free entries,
198     * it is only recognized as belonging to this pool now. It can then
199     * be passed to {@link #freeEntry freeEntry}.
200     *
201     * @param entry     the entry that was created for this pool
202     */
203    public void createdEntry(BasicPoolEntry entry) {
204
205        if (!route.equals(entry.getPlannedRoute())) {
206            throw new IllegalArgumentException
207                ("Entry not planned for this pool." +
208                 "\npool: " + route +
209                 "\nplan: " + entry.getPlannedRoute());
210        }
211
212        numEntries++;
213    }
214
215
216    /**
217     * Deletes an entry from this pool.
218     * Only entries that are currently free in this pool can be deleted.
219     * Allocated entries can not be deleted.
220     *
221     * @param entry     the entry to delete from this pool
222     *
223     * @return  <code>true</code> if the entry was found and deleted, or
224     *          <code>false</code> if the entry was not found
225     */
226    public boolean deleteEntry(BasicPoolEntry entry) {
227
228        final boolean found = freeEntries.remove(entry);
229        if (found)
230            numEntries--;
231        return found;
232    }
233
234
235    /**
236     * Forgets about an entry from this pool.
237     * This method is used to indicate that an entry
238     * {@link #allocEntry allocated}
239     * from this pool has been lost and will not be returned.
240     */
241    public void dropEntry() {
242        if (numEntries < 1) {
243            throw new IllegalStateException
244                ("There is no entry that could be dropped.");
245        }
246        numEntries--;
247    }
248
249
250    /**
251     * Adds a waiting thread.
252     * This pool makes no attempt to match waiting threads with pool entries.
253     * It is the caller's responsibility to check that there is no entry
254     * before adding a waiting thread.
255     *
256     * @param wt        the waiting thread
257     */
258    public void queueThread(WaitingThread wt) {
259        if (wt == null) {
260            throw new IllegalArgumentException
261                ("Waiting thread must not be null.");
262        }
263        this.waitingThreads.add(wt);
264    }
265
266
267    /**
268     * Checks whether there is a waiting thread in this pool.
269     *
270     * @return  <code>true</code> if there is a waiting thread,
271     *          <code>false</code> otherwise
272     */
273    public boolean hasThread() {
274        return !this.waitingThreads.isEmpty();
275    }
276
277
278    /**
279     * Returns the next thread in the queue.
280     *
281     * @return  a waiting thread, or <code>null</code> if there is none
282     */
283    public WaitingThread nextThread() {
284        return this.waitingThreads.peek();
285    }
286
287
288    /**
289     * Removes a waiting thread, if it is queued.
290     *
291     * @param wt        the waiting thread
292     */
293    public void removeThread(WaitingThread wt) {
294        if (wt == null)
295            return;
296
297        this.waitingThreads.remove(wt);
298    }
299
300
301} // class RouteSpecificPool
302