1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18// BEGIN android-note
19// This class was copied from a newer version of harmony
20// to improve reusability of URLConnections
21// END android-note
22
23package org.apache.harmony.luni.internal.net.www.protocol.http;
24
25import java.io.IOException;
26import java.net.Proxy;
27import java.net.URI;
28import java.security.AccessController;
29import java.util.ArrayList;
30import java.util.HashMap;
31import java.util.Iterator;
32import java.util.List;
33import java.util.Map;
34
35import org.apache.harmony.luni.util.PriviAction;
36
37/**
38 * <code>HttpConnectionManager</code> manages a pool of <code>HttpConnection</code>s
39 * that are not currently in use and is used to get hold of persistent <code>HttpConnection</code>s.
40 * Clients should return an <code>HttpConnection</code> to the pool after use by calling
41 * <code>returnConnectionToPool</code>
42 *
43 * Two system properties affect the behaviour of this class - <code>http.maxConnections</code>
44 * and <code>http.keepAlive</code>.  <code>http.keepAlive</code> determines whether
45 * or not connections should be persisted and <code>http.maxConnections</code>
46 * determines the maximum number of connections to each individual host that
47 * should be kept in the pool.
48 */
49public class HttpConnectionManager {
50
51    // The maximum number of connections to any location
52    private static int maxConnections = 5;
53
54    // Keeps connections alive if true
55    private static boolean keepAlive = true;
56
57    private static HttpConnectionManager defaultConnectionManager;
58    private ConnectionPool pool = new ConnectionPool();
59
60    /**
61     * Returns the default connection manager
62     */
63    public static HttpConnectionManager getDefault() {
64        if(defaultConnectionManager == null) {
65            defaultConnectionManager = new HttpConnectionManager();
66        }
67        return defaultConnectionManager;
68    }
69
70    public HttpConnection getConnection(URI uri, int connectTimeout) throws IOException {
71        checkSystemProperties();
72        HttpConfiguration config = new HttpConfiguration(uri);
73        return pool.getHttpConnection(config, connectTimeout);
74    }
75
76    public HttpConnection getConnection(URI uri, Proxy proxy, int connectTimeout) throws IOException {
77        checkSystemProperties();
78        HttpConfiguration config = new HttpConfiguration(uri, proxy);
79        return pool.getHttpConnection(config, connectTimeout);
80    }
81
82    public void returnConnectionToPool(HttpConnection connection) {
83        checkSystemProperties();
84        pool.returnConnection(connection);
85    }
86
87    public int numFreeConnections() {
88        return pool.numFreeConnections();
89    }
90
91    private void checkSystemProperties() {
92        String httpMaxConnections =  AccessController.doPrivileged(new PriviAction<String>("http.maxConnections"));
93        String httpKeepAlive = AccessController.doPrivileged(new PriviAction<String>("http.keepAlive"));
94        if(httpMaxConnections != null) {
95            maxConnections = Integer.parseInt(httpMaxConnections);
96        }
97        if(httpKeepAlive != null) {
98            keepAlive = Boolean.parseBoolean(httpKeepAlive);
99            if(!keepAlive) {
100                pool.clear();
101            }
102        }
103    }
104
105    private static class ConnectionPool {
106
107        private Map<HttpConfiguration, List<HttpConnection>> freeConnectionMap = new HashMap<HttpConfiguration, List<HttpConnection>>(); // Map of free Sockets
108
109        public synchronized void clear() {
110            for (Iterator<List<HttpConnection>> iter = freeConnectionMap.values().iterator(); iter.hasNext();) {
111                List<HttpConnection> connections = iter.next();
112                for (Iterator<HttpConnection> iterator = connections.iterator(); iterator.hasNext();) {
113                    HttpConnection connection = iterator.next();
114                    connection.closeSocketAndStreams();
115                }
116            }
117            freeConnectionMap.clear();
118        }
119
120        public synchronized void returnConnection(HttpConnection connection) {
121            // BEGIN android-note
122            // Simplified the following test.
123            // END android-note
124            if(keepAlive && connection.isEligibleForRecycling()) {
125                HttpConfiguration config = connection.getHttpConfiguration();
126                List<HttpConnection> connections = freeConnectionMap.get(config);
127                if(connections == null) {
128                    connections = new ArrayList<HttpConnection>();
129                    freeConnectionMap.put(config, connections);
130                }
131                if(connections.size() < HttpConnectionManager.maxConnections) {
132                    if(!connections.contains(connection)) {
133                        connections.add(connection);
134                    }
135                } else {
136                    connection.closeSocketAndStreams();
137                }
138            } else {
139                // Make sure all streams are closed etc.
140                connection.closeSocketAndStreams();
141            }
142        }
143
144        public synchronized HttpConnection getHttpConnection(HttpConfiguration config, int connectTimeout) throws IOException {
145            List<HttpConnection> connections = freeConnectionMap.get(config);
146            if(keepAlive && connections == null) {
147                connections = new ArrayList<HttpConnection>();
148                freeConnectionMap.put(config, connections);
149            }
150            if(!keepAlive || connections.isEmpty()) {
151                HttpConnection connection = new HttpConnection(config, connectTimeout);
152                return connection;
153            } else {
154                HttpConnection connection = connections.get(0);
155                connections.remove(0);
156                if(!connection.isStale()) {
157                    SecurityManager security = System.getSecurityManager();
158                    if (security != null) {
159                        security.checkConnect(connection.getSocket().getInetAddress().getHostName(), connection.getSocket().getPort());
160                    }
161                    return connection;
162                } else {
163                    return getHttpConnection(config, connectTimeout);
164                }
165            }
166        }
167
168        public int numFreeConnections() {
169            int numFree = 0;
170            for (Iterator<List<HttpConnection>> iter = freeConnectionMap.values().iterator(); iter.hasNext();) {
171                List<HttpConnection> connections = iter.next();
172                numFree += connections.size();
173            }
174            return numFree;
175        }
176    }
177
178    public void reset() {
179        pool.clear();
180    }
181
182}
183