View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.transport.apache;
20  
21  import javax.net.ssl.HostnameVerifier;
22  import javax.net.ssl.SSLSocketFactory;
23  
24  import java.io.Closeable;
25  import java.util.Arrays;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.ConcurrentMap;
30  import java.util.concurrent.TimeUnit;
31  
32  import org.apache.http.HttpHost;
33  import org.apache.http.config.RegistryBuilder;
34  import org.apache.http.conn.HttpClientConnectionManager;
35  import org.apache.http.conn.socket.ConnectionSocketFactory;
36  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
37  import org.apache.http.conn.ssl.NoopHostnameVerifier;
38  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
39  import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator;
40  import org.apache.http.impl.conn.DefaultSchemePortResolver;
41  import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
42  import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
43  import org.apache.http.impl.conn.SystemDefaultDnsResolver;
44  import org.apache.http.ssl.SSLContextBuilder;
45  import org.apache.http.ssl.SSLInitializationException;
46  import org.eclipse.aether.ConfigurationProperties;
47  import org.eclipse.aether.RepositoryCache;
48  import org.eclipse.aether.RepositorySystemSession;
49  import org.eclipse.aether.util.ConfigUtils;
50  
51  /**
52   * Container for HTTP-related state that can be shared across incarnations of the transporter to optimize the
53   * communication with servers.
54   */
55  final class GlobalState implements Closeable {
56  
57      static class CompoundKey {
58  
59          private final Object[] keys;
60  
61          CompoundKey(Object... keys) {
62              this.keys = keys;
63          }
64  
65          @Override
66          public boolean equals(Object obj) {
67              if (this == obj) {
68                  return true;
69              }
70              if (obj == null || !getClass().equals(obj.getClass())) {
71                  return false;
72              }
73              CompoundKey that = (CompoundKey) obj;
74              return Arrays.equals(keys, that.keys);
75          }
76  
77          @Override
78          public int hashCode() {
79              int hash = 17;
80              hash = hash * 31 + Arrays.hashCode(keys);
81              return hash;
82          }
83  
84          @Override
85          public String toString() {
86              return Arrays.toString(keys);
87          }
88      }
89  
90      private static final String KEY = GlobalState.class.getName();
91  
92      private static final String CONFIG_PROP_CACHE_STATE =
93              ApacheTransporterConfigurationKeys.CONFIG_PROPS_PREFIX + "cacheState";
94  
95      private final ConcurrentMap<ConnMgrConfig, HttpClientConnectionManager> connectionManagers;
96  
97      private final ConcurrentMap<CompoundKey, Object> userTokens;
98  
99      private final ConcurrentMap<HttpHost, AuthSchemePool> authSchemePools;
100 
101     private final ConcurrentMap<CompoundKey, Boolean> expectContinues;
102 
103     public static GlobalState get(RepositorySystemSession session) {
104         GlobalState cache;
105         RepositoryCache repoCache = session.getCache();
106         if (repoCache == null || !ConfigUtils.getBoolean(session, true, CONFIG_PROP_CACHE_STATE)) {
107             cache = null;
108         } else {
109             Object tmp = repoCache.get(session, KEY);
110             if (tmp instanceof GlobalState) {
111                 cache = (GlobalState) tmp;
112             } else {
113                 synchronized (GlobalState.class) {
114                     tmp = repoCache.get(session, KEY);
115                     if (tmp instanceof GlobalState) {
116                         cache = (GlobalState) tmp;
117                     } else {
118                         cache = new GlobalState();
119                         repoCache.put(session, KEY, cache);
120                     }
121                 }
122             }
123         }
124         return cache;
125     }
126 
127     private GlobalState() {
128         connectionManagers = new ConcurrentHashMap<>();
129         userTokens = new ConcurrentHashMap<>();
130         authSchemePools = new ConcurrentHashMap<>();
131         expectContinues = new ConcurrentHashMap<>();
132     }
133 
134     @Override
135     public void close() {
136         for (Iterator<Map.Entry<ConnMgrConfig, HttpClientConnectionManager>> it =
137                         connectionManagers.entrySet().iterator();
138                 it.hasNext(); ) {
139             HttpClientConnectionManager connMgr = it.next().getValue();
140             it.remove();
141             connMgr.shutdown();
142         }
143     }
144 
145     public HttpClientConnectionManager getConnectionManager(ConnMgrConfig config) {
146         return connectionManagers.computeIfAbsent(config, GlobalState::newConnectionManager);
147     }
148 
149     @SuppressWarnings("checkstyle:magicnumber")
150     public static HttpClientConnectionManager newConnectionManager(ConnMgrConfig connMgrConfig) {
151         RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
152                 .register("http", PlainConnectionSocketFactory.getSocketFactory());
153         int connectionMaxTtlSeconds = ConfigurationProperties.DEFAULT_HTTP_CONNECTION_MAX_TTL;
154         int maxConnectionsPerRoute = ConfigurationProperties.DEFAULT_HTTP_MAX_CONNECTIONS_PER_ROUTE;
155 
156         if (connMgrConfig == null) {
157             registryBuilder.register("https", SSLConnectionSocketFactory.getSystemSocketFactory());
158         } else {
159             // config present: use provided, if any, or create (depending on httpsSecurityMode)
160             connectionMaxTtlSeconds = connMgrConfig.connectionMaxTtlSeconds;
161             maxConnectionsPerRoute = connMgrConfig.maxConnectionsPerRoute;
162             SSLSocketFactory sslSocketFactory =
163                     connMgrConfig.context != null ? connMgrConfig.context.getSocketFactory() : null;
164             HostnameVerifier hostnameVerifier = connMgrConfig.verifier;
165             if (ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(connMgrConfig.httpsSecurityMode)) {
166                 if (sslSocketFactory == null) {
167                     sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
168                 }
169                 if (hostnameVerifier == null) {
170                     hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
171                 }
172             } else if (ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(connMgrConfig.httpsSecurityMode)) {
173                 if (sslSocketFactory == null) {
174                     try {
175                         sslSocketFactory = new SSLContextBuilder()
176                                 .loadTrustMaterial(null, (chain, auth) -> true)
177                                 .build()
178                                 .getSocketFactory();
179                     } catch (Exception e) {
180                         throw new SSLInitializationException(
181                                 "Could not configure '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode", e);
182                     }
183                 }
184                 if (hostnameVerifier == null) {
185                     hostnameVerifier = NoopHostnameVerifier.INSTANCE;
186                 }
187             } else {
188                 throw new IllegalArgumentException(
189                         "Unsupported '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode.");
190             }
191 
192             registryBuilder.register(
193                     "https",
194                     new SSLConnectionSocketFactory(
195                             sslSocketFactory, connMgrConfig.protocols, connMgrConfig.cipherSuites, hostnameVerifier));
196         }
197 
198         PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(
199                 new DefaultHttpClientConnectionOperator(
200                         registryBuilder.build(), DefaultSchemePortResolver.INSTANCE, SystemDefaultDnsResolver.INSTANCE),
201                 ManagedHttpClientConnectionFactory.INSTANCE,
202                 connectionMaxTtlSeconds,
203                 TimeUnit.SECONDS);
204         connMgr.setMaxTotal(maxConnectionsPerRoute * 2);
205         connMgr.setDefaultMaxPerRoute(maxConnectionsPerRoute);
206         return connMgr;
207     }
208 
209     public Object getUserToken(CompoundKey key) {
210         return userTokens.get(key);
211     }
212 
213     public void setUserToken(CompoundKey key, Object userToken) {
214         if (userToken != null) {
215             userTokens.put(key, userToken);
216         } else {
217             userTokens.remove(key);
218         }
219     }
220 
221     public ConcurrentMap<HttpHost, AuthSchemePool> getAuthSchemePools() {
222         return authSchemePools;
223     }
224 
225     public Boolean getExpectContinue(CompoundKey key) {
226         return expectContinues.get(key);
227     }
228 
229     public void setExpectContinue(CompoundKey key, boolean enabled) {
230         expectContinues.put(key, enabled);
231     }
232 }