1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java $
3 * $Revision: 673450 $
4 * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 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 */
30package org.apache.http.impl.conn;
31
32import java.io.IOException;
33import java.util.HashMap;
34import java.util.Iterator;
35import java.util.Map;
36import java.util.concurrent.TimeUnit;
37
38import org.apache.commons.logging.Log;
39import org.apache.commons.logging.LogFactory;
40import org.apache.http.HttpConnection;
41
42
43/**
44 * A helper class for connection managers to track idle connections.
45 *
46 * <p>This class is not synchronized.</p>
47 *
48 * @see org.apache.http.conn.ClientConnectionManager#closeIdleConnections
49 *
50 * @since 4.0
51 */
52public class IdleConnectionHandler {
53
54    private final Log log = LogFactory.getLog(getClass());
55
56    /** Holds connections and the time they were added. */
57    private final Map<HttpConnection,TimeValues> connectionToTimes;
58
59
60    public IdleConnectionHandler() {
61        super();
62        connectionToTimes = new HashMap<HttpConnection,TimeValues>();
63    }
64
65    /**
66     * Registers the given connection with this handler.  The connection will be held until
67     * {@link #remove} or {@link #closeIdleConnections} is called.
68     *
69     * @param connection the connection to add
70     *
71     * @see #remove
72     */
73    public void add(HttpConnection connection, long validDuration, TimeUnit unit) {
74
75        Long timeAdded = Long.valueOf(System.currentTimeMillis());
76
77        if (log.isDebugEnabled()) {
78            log.debug("Adding connection at: " + timeAdded);
79        }
80
81        connectionToTimes.put(connection, new TimeValues(timeAdded, validDuration, unit));
82    }
83
84    /**
85     * Removes the given connection from the list of connections to be closed when idle.
86     * This will return true if the connection is still valid, and false
87     * if the connection should be considered expired and not used.
88     *
89     * @param connection
90     * @return True if the connection is still valid.
91     */
92    public boolean remove(HttpConnection connection) {
93        TimeValues times = connectionToTimes.remove(connection);
94        if(times == null) {
95            log.warn("Removing a connection that never existed!");
96            return true;
97        } else {
98            return System.currentTimeMillis() <= times.timeExpires;
99        }
100    }
101
102    /**
103     * Removes all connections referenced by this handler.
104     */
105    public void removeAll() {
106        this.connectionToTimes.clear();
107    }
108
109    /**
110     * Closes connections that have been idle for at least the given amount of time.
111     *
112     * @param idleTime the minimum idle time, in milliseconds, for connections to be closed
113     */
114    //@@@ add TimeUnit argument here?
115    public void closeIdleConnections(long idleTime) {
116
117        // the latest time for which connections will be closed
118        long idleTimeout = System.currentTimeMillis() - idleTime;
119
120        if (log.isDebugEnabled()) {
121            log.debug("Checking for connections, idleTimeout: "  + idleTimeout);
122        }
123
124        Iterator<HttpConnection> connectionIter =
125            connectionToTimes.keySet().iterator();
126
127        while (connectionIter.hasNext()) {
128            HttpConnection conn = connectionIter.next();
129            TimeValues times = connectionToTimes.get(conn);
130            Long connectionTime = times.timeAdded;
131            if (connectionTime.longValue() <= idleTimeout) {
132                if (log.isDebugEnabled()) {
133                    log.debug("Closing connection, connection time: "  + connectionTime);
134                }
135                connectionIter.remove();
136                try {
137                    conn.close();
138                } catch (IOException ex) {
139                    log.debug("I/O error closing connection", ex);
140                }
141            }
142        }
143    }
144
145
146    public void closeExpiredConnections() {
147        long now = System.currentTimeMillis();
148        if (log.isDebugEnabled()) {
149            log.debug("Checking for expired connections, now: "  + now);
150        }
151
152        Iterator<HttpConnection> connectionIter =
153            connectionToTimes.keySet().iterator();
154
155        while (connectionIter.hasNext()) {
156            HttpConnection conn = connectionIter.next();
157            TimeValues times = connectionToTimes.get(conn);
158            if(times.timeExpires <= now) {
159                if (log.isDebugEnabled()) {
160                    log.debug("Closing connection, expired @: "  + times.timeExpires);
161                }
162                connectionIter.remove();
163                try {
164                    conn.close();
165                } catch (IOException ex) {
166                    log.debug("I/O error closing connection", ex);
167                }
168            }
169        }
170    }
171
172    private static class TimeValues {
173        private final long timeAdded;
174        private final long timeExpires;
175
176        /**
177         * @param now The current time in milliseconds
178         * @param validDuration The duration this connection is valid for
179         * @param validUnit The unit of time the duration is specified in.
180         */
181        TimeValues(long now, long validDuration, TimeUnit validUnit) {
182            this.timeAdded = now;
183            if(validDuration > 0) {
184                this.timeExpires = now + validUnit.toMillis(validDuration);
185            } else {
186                this.timeExpires = Long.MAX_VALUE;
187            }
188        }
189    }
190}
191