View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2005, 2013
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Roberto Pineiro, IBM, roberto.pineiro@us.ibm.com
12   * @author : Chung-hao Tan, IBM, chungtan@us.ibm.com
13   * 
14   * 
15   * Change History
16   * Flag       Date        Prog         Description
17   *------------------------------------------------------------------------------- 
18   * 1353168    2005-11-24  fiuczy       Possible NullPointerExcection in HttpClient.streamFinished()
19   * 1498130    2006-05-31  lupusalex    Selection of xml parser on a per connection basis
20   * 1535756    2006-08-07  lupusalex    Make code warning free
21   * 1565892    2006-11-28  lupusalex    Make SBLIM client JSR48 compliant
22   * 1646434    2007-01-28  lupusalex    CIMClient close() invalidates all it's enumerations
23   * 1647159    2007-01-29  lupusalex    HttpClientPool runs out of HttpClients
24   * 1660743    2007-02-15  lupusalex    SSLContext is static
25   * 1714902    2007-05-08  lupusalex    Threading related weak spots
26   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
27   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
28   * 2531371    2009-02-10  raman_arora  Upgrade client to JDK 1.5 (Phase 2)
29   * 3400209    2011-08-31  blaschke-oss Highlighted Static Analysis (PMD) issues
30   *    2618    2013-02-27  blaschke-oss Need to add property to disable weak cipher suites for the secure indication
31   *    2642    2013-05-21  blaschke-oss Seperate properties needed for cim client and listener to filter out ciphers
32   */
33  
34  package org.metricshub.wbem.sblim.cimclient.internal.http;
35  
36  /*-
37   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
38   * WBEM Java Client
39   * ჻჻჻჻჻჻
40   * Copyright 2023 - 2025 MetricsHub
41   * ჻჻჻჻჻჻
42   * Licensed under the Apache License, Version 2.0 (the "License");
43   * you may not use this file except in compliance with the License.
44   * You may obtain a copy of the License at
45   *
46   *      http://www.apache.org/licenses/LICENSE-2.0
47   *
48   * Unless required by applicable law or agreed to in writing, software
49   * distributed under the License is distributed on an "AS IS" BASIS,
50   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
51   * See the License for the specific language governing permissions and
52   * limitations under the License.
53   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
54   */
55  
56  import java.net.URI;
57  import java.util.ArrayList;
58  import java.util.Iterator;
59  import java.util.List;
60  import java.util.logging.Level;
61  import javax.net.ssl.SSLContext;
62  import org.metricshub.wbem.sblim.cimclient.internal.logging.LogAndTraceBroker;
63  import org.metricshub.wbem.sblim.cimclient.internal.util.Util;
64  import org.metricshub.wbem.sblim.cimclient.internal.util.WBEMConfiguration;
65  
66  /**
67   * Class HttpClientPool implements a pool for http client connections
68   *
69   */
70  public class HttpClientPool {
71  	/**
72  	 * Guarded by "this"
73  	 */
74  	private List<HttpClient> iAllConnections;
75  
76  	/**
77  	 * Guarded by "this"
78  	 */
79  	private List<HttpClient> iAvailableConnections;
80  
81  	/**
82  	 * Guarded by "this"
83  	 */
84  	private boolean iClosed = false;
85  
86  	private final int iPoolSize;
87  
88  	private final WBEMConfiguration iConfiguration;
89  
90  	/**
91  	 * Guarded by "this"
92  	 */
93  	private SSLContext iSslContext;
94  
95  	private String[] iEnabledCipherSuites = null;
96  
97  	/**
98  	 * Ctor.
99  	 *
100 	 * @param pConfiguration
101 	 *            The configuration for this session. Must be non-null.
102 	 */
103 	public HttpClientPool(WBEMConfiguration pConfiguration) {
104 		super();
105 		this.iConfiguration = pConfiguration;
106 		this.iAllConnections = new ArrayList<HttpClient>(pConfiguration.getHttpPoolSize());
107 		this.iAvailableConnections = new ArrayList<HttpClient>(pConfiguration.getHttpPoolSize());
108 		this.iPoolSize = pConfiguration.getHttpPoolSize();
109 		this.iSslContext = null;
110 		this.iEnabledCipherSuites = null;
111 	}
112 
113 	/**
114 	 * Returns the number of connections in this pool that are available/free
115 	 * for (re-)use.
116 	 *
117 	 * @return number of available/free connections in pool
118 	 */
119 	public synchronized int getNumberOfAllConnections() {
120 		return this.iAllConnections.size();
121 	}
122 
123 	/**
124 	 * Returns the number of all connections in this pool.
125 	 *
126 	 * @return number of all connections in pool
127 	 */
128 	public synchronized int getNumberOfAvailableConnections() {
129 		return this.iAvailableConnections.size();
130 	}
131 
132 	/**
133 	 * Returns the configuration context of this pool
134 	 *
135 	 * @return The configuration
136 	 */
137 	public WBEMConfiguration getConfigurationContext() {
138 		return this.iConfiguration;
139 	}
140 
141 	/**
142 	 * Returns the SSL context for the clients in this pool. The context is
143 	 * initialized on the first call of this method (lazy initialization).
144 	 *
145 	 * @return The SSL context
146 	 */
147 	public synchronized SSLContext getSslContext() {
148 		if (this.iSslContext == null) {
149 			this.iSslContext = HttpSocketFactory.getInstance().getClientSSLContext(this.iConfiguration);
150 		}
151 		return this.iSslContext;
152 	}
153 
154 	/**
155 	 * Returns the available connections of this pool for a given
156 	 * URI&AuthorizationHandler
157 	 *
158 	 * @param pUri
159 	 *            The uri
160 	 * @param pHandler
161 	 *            The authorization handler
162 	 * @return A connection if one is available, <code>null</code> otherwise
163 	 */
164 	public synchronized HttpClient retrieveAvailableConnectionFromPool(URI pUri, AuthorizationHandler pHandler) {
165 		if (this.iClosed) {
166 			LogAndTraceBroker.getBroker().trace(Level.FINE, "Attempt to get client from closed http client pool,");
167 			throw new IllegalStateException("HttpClientPool is closed. Retrieve connection failed.");
168 		}
169 		if (getNumberOfAvailableConnections() > 0) {
170 			LogAndTraceBroker
171 				.getBroker()
172 				.trace(
173 					Level.FINE,
174 					"Reusing client (" +
175 					pUri.toString() +
176 					", max: " +
177 					getPoolSize() +
178 					", current:" +
179 					getNumberOfAvailableConnections()
180 				);
181 
182 			return this.iAvailableConnections.remove(0);
183 		}
184 
185 		LogAndTraceBroker
186 			.getBroker()
187 			.trace(
188 				Level.FINE,
189 				"New client (" + pUri.toString() + ", max: " + getPoolSize() + ", current:" + getNumberOfAvailableConnections()
190 			);
191 		HttpClient client = new HttpClient(pUri, this, pHandler);
192 		addConnectionToPool(client);
193 		return client;
194 	}
195 
196 	/**
197 	 * Add the connection to the pool. Connection is added as available
198 	 * connection. Use method {@link #addConnectionToPool(HttpClient)} to add
199 	 * the connection without being available for reuse.
200 	 *
201 	 * @param httpClient
202 	 *            connection that is to be added to the pool
203 	 * @return true if connection was added otherwise false
204 	 */
205 	public synchronized boolean returnAvailableConnectionToPool(HttpClient httpClient) {
206 		if (httpClient == null) {
207 			return false;
208 		}
209 
210 		if (this.iClosed) {
211 			this.iAllConnections.remove(httpClient);
212 			httpClient.disconnect();
213 			return false;
214 		}
215 
216 		if (getPoolSize() > this.iAvailableConnections.size()) {
217 			if (!this.iAvailableConnections.contains(httpClient)) {
218 				// ensure that the connection exists in allConnections owned by
219 				// pool
220 				addConnectionToPool(httpClient);
221 				this.iAvailableConnections.add(httpClient);
222 				return true;
223 			}
224 		} else {
225 			LogAndTraceBroker.getBroker().trace(Level.FINE, "Http pool size reached, discarding client.");
226 			this.iAllConnections.remove(httpClient);
227 			this.iAvailableConnections.remove(httpClient);
228 			httpClient.disconnect();
229 		}
230 		return false; // connection was not added to pool
231 	}
232 
233 	/**
234 	 * Add the connection to the pool, but does NOT add it as available
235 	 * connection. Use method
236 	 * {@link #returnAvailableConnectionToPool(HttpClient)} to also add the
237 	 * connection to the available connections.
238 	 *
239 	 * @param httpClient
240 	 *            connection that is to be added to the pool
241 	 * @return true if connection was added otherwise false
242 	 */
243 	public synchronized boolean addConnectionToPool(HttpClient httpClient) {
244 		if (this.iClosed) {
245 			LogAndTraceBroker.getBroker().trace(Level.FINE, "Attempt to add client to closed http client pool,");
246 			throw new IllegalStateException("HttpClientPool is closed. Add connection failed.");
247 		}
248 		if (httpClient != null && !this.iAllConnections.contains(httpClient)) {
249 			// if connection is not in pool add it
250 			this.iAllConnections.add(httpClient);
251 			return true;
252 		}
253 		return false;
254 	}
255 
256 	/**
257 	 * Removes a connection completely from the pool.
258 	 *
259 	 * @param httpClient
260 	 *            connection that is to be removed from the pool
261 	 * @return true if connection was removed otherwise false
262 	 */
263 	public synchronized boolean removeConnectionFromPool(HttpClient httpClient) {
264 		if (httpClient != null) {
265 			this.iAvailableConnections.remove(httpClient);
266 			if (this.iAllConnections.remove(httpClient)) {
267 				return true;
268 			}
269 			return false; // connection was not in pool!
270 		}
271 		return false; // no connection given
272 	}
273 
274 	/**
275 	 * Closes the pool. This will disconnect all clients in the pool.
276 	 */
277 	public synchronized void closePool() {
278 		this.iClosed = true;
279 		Iterator<HttpClient> iter = this.iAvailableConnections.iterator();
280 		while (iter.hasNext()) {
281 			HttpClient httpClient = iter.next();
282 			this.iAllConnections.remove(httpClient);
283 			httpClient.disconnect();
284 		}
285 		this.iAvailableConnections.clear();
286 	}
287 
288 	@Override
289 	protected void finalize() throws Throwable {
290 		closePool();
291 		super.finalize();
292 	}
293 
294 	/**
295 	 * Returns poolSize
296 	 *
297 	 * @return The value of poolSize.
298 	 */
299 	public int getPoolSize() {
300 		return this.iPoolSize;
301 	}
302 
303 	/**
304 	 * Returns updated array of cipher suites which is current cipher suites
305 	 * less any cipher suites listed to be disabled
306 	 *
307 	 * NOTE: The updated array is generated only upon first invocation and then
308 	 * saved, effectively making this a lazy initialization of the cipher suites
309 	 * on a HttpClientPool basis - it has to be done here and not in WBEMClient
310 	 * where it belongs because socket characteristics are not known to
311 	 * WBEMClient
312 	 *
313 	 * @param pCurrentCipherSuites
314 	 *            The currently enabled cipher suites
315 	 * @param pDisableCipherSuites
316 	 *            The list of cipher suites to be disabled
317 	 * @return The updated enabled cipher suites
318 	 */
319 	public synchronized String[] getUpdatedCipherSuites(String[] pCurrentCipherSuites, String pDisableCipherSuites) {
320 		if (this.iEnabledCipherSuites == null) {
321 			this.iEnabledCipherSuites = Util.getFilteredStringArray(pCurrentCipherSuites, pDisableCipherSuites);
322 			int before = pCurrentCipherSuites.length;
323 			int after = this.iEnabledCipherSuites.length;
324 			if (before > 0 && after == 0) LogAndTraceBroker
325 				.getBroker()
326 				.trace(Level.WARNING, "All cipher suites disabled for client!"); else if (before > after) LogAndTraceBroker
327 				.getBroker()
328 				.trace(Level.FINE, "Some (" + (before - after) + ") cipher suites disabled for client"); else if (
329 				before == after
330 			) LogAndTraceBroker.getBroker().trace(Level.FINER, "No cipher suites disabled for client");
331 		}
332 		return this.iEnabledCipherSuites;
333 	}
334 }