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.config.RegistryBuilder;
33  import org.apache.http.conn.HttpClientConnectionManager;
34  import org.apache.http.conn.socket.ConnectionSocketFactory;
35  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
36  import org.apache.http.conn.ssl.NoopHostnameVerifier;
37  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
38  import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator;
39  import org.apache.http.impl.conn.DefaultSchemePortResolver;
40  import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
41  import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
42  import org.apache.http.impl.conn.SystemDefaultDnsResolver;
43  import org.apache.http.ssl.SSLContextBuilder;
44  import org.apache.http.ssl.SSLInitializationException;
45  import org.eclipse.aether.ConfigurationProperties;
46  import org.eclipse.aether.RepositoryCache;
47  import org.eclipse.aether.RepositorySystemSession;
48  import org.eclipse.aether.util.ConfigUtils;
49  
50  /**
51   * Container for HTTP-related state that can be shared across incarnations of the transporter to optimize the
52   * communication with servers.
53   */
54  final class GlobalState implements Closeable {
55  
56      static class CompoundKey {
57  
58          private final Object[] keys;
59  
60          CompoundKey(Object... keys) {
61              this.keys = keys;
62          }
63  
64          @Override
65          public boolean equals(Object obj) {
66              if (this == obj) {
67                  return true;
68              }
69              if (obj == null || !getClass().equals(obj.getClass())) {
70                  return false;
71              }
72              CompoundKey that = (CompoundKey) obj;
73              return Arrays.equals(keys, that.keys);
74          }
75  
76          @Override
77          public int hashCode() {
78              int hash = 17;
79              hash = hash * 31 + Arrays.hashCode(keys);
80              return hash;
81          }
82  
83          @Override
84          public String toString() {
85              return Arrays.toString(keys);
86          }
87      }
88  
89      private static final String KEY = GlobalState.class.getName();
90  
91      private static final String CONFIG_PROP_CACHE_STATE =
92              ApacheTransporterConfigurationKeys.CONFIG_PROPS_PREFIX + "cacheState";
93  
94      private final ConcurrentMap<ConnMgrConfig, HttpClientConnectionManager> connectionManagers;
95  
96      private final ConcurrentMap<CompoundKey, Object> userTokens;
97  
98      private final ConcurrentMap<CompoundKey, Boolean> expectContinues;
99  
100     public static GlobalState get(RepositorySystemSession session) {
101         GlobalState cache;
102         RepositoryCache repoCache = session.getCache();
103         if (repoCache == null || !ConfigUtils.getBoolean(session, true, CONFIG_PROP_CACHE_STATE)) {
104             cache = null;
105         } else {
106             Object tmp = repoCache.get(session, KEY);
107             if (tmp instanceof GlobalState) {
108                 cache = (GlobalState) tmp;
109             } else {
110                 synchronized (GlobalState.class) {
111                     tmp = repoCache.get(session, KEY);
112                     if (tmp instanceof GlobalState) {
113                         cache = (GlobalState) tmp;
114                     } else {
115                         cache = new GlobalState();
116                         repoCache.put(session, KEY, cache);
117                     }
118                 }
119             }
120         }
121         return cache;
122     }
123 
124     private GlobalState() {
125         connectionManagers = new ConcurrentHashMap<>();
126         userTokens = new ConcurrentHashMap<>();
127         expectContinues = new ConcurrentHashMap<>();
128     }
129 
130     @Override
131     public void close() {
132         for (Iterator<Map.Entry<ConnMgrConfig, HttpClientConnectionManager>> it =
133                         connectionManagers.entrySet().iterator();
134                 it.hasNext(); ) {
135             HttpClientConnectionManager connMgr = it.next().getValue();
136             it.remove();
137             connMgr.shutdown();
138         }
139     }
140 
141     public HttpClientConnectionManager getConnectionManager(ConnMgrConfig config) {
142         return connectionManagers.computeIfAbsent(config, GlobalState::newConnectionManager);
143     }
144 
145     public static HttpClientConnectionManager newConnectionManager(ConnMgrConfig connMgrConfig) {
146         RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
147                 .register("http", PlainConnectionSocketFactory.getSocketFactory());
148         int connectionMaxTtlSeconds = ConfigurationProperties.DEFAULT_HTTP_CONNECTION_MAX_TTL;
149         int maxConnectionsPerRoute = ConfigurationProperties.DEFAULT_HTTP_MAX_CONNECTIONS_PER_ROUTE;
150 
151         if (connMgrConfig == null) {
152             registryBuilder.register("https", SSLConnectionSocketFactory.getSystemSocketFactory());
153         } else {
154             // config present: use provided, if any, or create (depending on httpsSecurityMode)
155             connectionMaxTtlSeconds = connMgrConfig.connectionMaxTtlSeconds;
156             maxConnectionsPerRoute = connMgrConfig.maxConnectionsPerRoute;
157             SSLSocketFactory sslSocketFactory =
158                     connMgrConfig.context != null ? connMgrConfig.context.getSocketFactory() : null;
159             HostnameVerifier hostnameVerifier = connMgrConfig.verifier;
160             if (ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(connMgrConfig.httpsSecurityMode)) {
161                 if (sslSocketFactory == null) {
162                     sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
163                 }
164                 if (hostnameVerifier == null) {
165                     hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
166                 }
167             } else if (ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(connMgrConfig.httpsSecurityMode)) {
168                 if (sslSocketFactory == null) {
169                     try {
170                         sslSocketFactory = new SSLContextBuilder()
171                                 .loadTrustMaterial(null, (chain, auth) -> true)
172                                 .build()
173                                 .getSocketFactory();
174                     } catch (Exception e) {
175                         throw new SSLInitializationException(
176                                 "Could not configure '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode", e);
177                     }
178                 }
179                 if (hostnameVerifier == null) {
180                     hostnameVerifier = NoopHostnameVerifier.INSTANCE;
181                 }
182             } else {
183                 throw new IllegalArgumentException(
184                         "Unsupported '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode.");
185             }
186 
187             registryBuilder.register(
188                     "https",
189                     new SSLConnectionSocketFactory(
190                             sslSocketFactory, connMgrConfig.protocols, connMgrConfig.cipherSuites, hostnameVerifier));
191         }
192 
193         PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(
194                 new DefaultHttpClientConnectionOperator(
195                         registryBuilder.build(), DefaultSchemePortResolver.INSTANCE, SystemDefaultDnsResolver.INSTANCE),
196                 ManagedHttpClientConnectionFactory.INSTANCE,
197                 connectionMaxTtlSeconds,
198                 TimeUnit.SECONDS);
199         connMgr.setMaxTotal(maxConnectionsPerRoute * 2);
200         connMgr.setDefaultMaxPerRoute(maxConnectionsPerRoute);
201         return connMgr;
202     }
203 
204     public Object getUserToken(CompoundKey key) {
205         return userTokens.get(key);
206     }
207 
208     public void setUserToken(CompoundKey key, Object userToken) {
209         if (userToken != null) {
210             userTokens.put(key, userToken);
211         } else {
212             userTokens.remove(key);
213         }
214     }
215 
216     public Boolean getExpectContinue(CompoundKey key) {
217         return expectContinues.get(key);
218     }
219 
220     public void setExpectContinue(CompoundKey key, boolean enabled) {
221         expectContinues.put(key, enabled);
222     }
223 }