View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.auth;
28  
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.Objects;
32  import java.util.concurrent.ConcurrentHashMap;
33  
34  import org.apache.hc.client5.http.SchemePortResolver;
35  import org.apache.hc.client5.http.auth.AuthCache;
36  import org.apache.hc.client5.http.auth.AuthScheme;
37  import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
38  import org.apache.hc.client5.http.impl.StateHolder;
39  import org.apache.hc.core5.annotation.Contract;
40  import org.apache.hc.core5.annotation.ThreadingBehavior;
41  import org.apache.hc.core5.http.HttpHost;
42  import org.apache.hc.core5.net.NamedEndpoint;
43  import org.apache.hc.core5.util.Args;
44  import org.apache.hc.core5.util.LangUtils;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  /**
49   * Default implementation of {@link AuthCache}. This implements
50   * expects {@link org.apache.hc.client5.http.auth.AuthScheme} to be {@link java.io.Serializable}
51   * in order to be cacheable.
52   * <p>
53   * Instances of this class are thread safe as of version 4.4.
54   * </p>
55   *
56   * @since 4.1
57   */
58  @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
59  public class BasicAuthCache implements AuthCache {
60  
61      private static final Logger LOG = LoggerFactory.getLogger(BasicAuthCache.class);
62  
63      static class Key {
64  
65          final String scheme;
66          final String host;
67          final int port;
68          final String pathPrefix;
69  
70          Key(final String scheme, final String host, final int port, final String pathPrefix) {
71              Args.notBlank(scheme, "Scheme");
72              Args.notBlank(host, "Scheme");
73              this.scheme = scheme.toLowerCase(Locale.ROOT);
74              this.host = host.toLowerCase(Locale.ROOT);
75              this.port = port;
76              this.pathPrefix = pathPrefix;
77          }
78  
79          @Override
80          public boolean equals(final Object obj) {
81              if (this == obj) {
82                  return true;
83              }
84              if (obj instanceof Key) {
85                  final Key that = (Key) obj;
86                  return this.scheme.equals(that.scheme) &&
87                          this.host.equals(that.host) &&
88                          this.port == that.port &&
89                          Objects.equals(this.pathPrefix, that.pathPrefix);
90              }
91              return false;
92          }
93  
94          @Override
95          public int hashCode() {
96              int hash = LangUtils.HASH_SEED;
97              hash = LangUtils.hashCode(hash, this.scheme);
98              hash = LangUtils.hashCode(hash, this.host);
99              hash = LangUtils.hashCode(hash, this.port);
100             hash = LangUtils.hashCode(hash, this.pathPrefix);
101             return hash;
102         }
103 
104         @Override
105         public String toString() {
106             final StringBuilder buf = new StringBuilder();
107             buf.append(scheme).append("://").append(host);
108             if (port >= 0) {
109                 buf.append(":").append(port);
110             }
111             if (pathPrefix != null) {
112                 if (!pathPrefix.startsWith("/")) {
113                     buf.append("/");
114                 }
115                 buf.append(pathPrefix);
116             }
117             return buf.toString();
118         }
119     }
120 
121     static class AuthData {
122 
123         final Class<? extends AuthScheme> clazz;
124         final Object state;
125 
126         public AuthData(final Class<? extends AuthScheme> clazz, final Object state) {
127             this.clazz = clazz;
128             this.state = state;
129         }
130 
131     }
132 
133     private final Map<Key, AuthData> map;
134     private final SchemePortResolver schemePortResolver;
135 
136     /**
137      * Default constructor.
138      *
139      * @since 4.3
140      */
141     public BasicAuthCache(final SchemePortResolver schemePortResolver) {
142         super();
143         this.map = new ConcurrentHashMap<>();
144         this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
145     }
146 
147     public BasicAuthCache() {
148         this(null);
149     }
150 
151     private Key key(final String scheme, final NamedEndpoint authority, final String pathPrefix) {
152         return new Key(scheme, authority.getHostName(), schemePortResolver.resolve(scheme, authority), pathPrefix);
153     }
154 
155     private AuthData data(final AuthScheme authScheme) {
156         return new AuthData(authScheme.getClass(), ((StateHolder) authScheme).store());
157     }
158 
159     @Override
160     public void put(final HttpHost host, final AuthScheme authScheme) {
161         put(host, null, authScheme);
162     }
163 
164     @Override
165     public AuthScheme get(final HttpHost host) {
166         return get(host, null);
167     }
168 
169     @Override
170     public void remove(final HttpHost host) {
171         remove(host, null);
172     }
173 
174     @Override
175     public void put(final HttpHost host, final String pathPrefix, final AuthScheme authScheme) {
176         Args.notNull(host, "HTTP host");
177         if (authScheme == null) {
178             return;
179         }
180         if (authScheme instanceof StateHolder) {
181             this.map.put(key(host.getSchemeName(), host, pathPrefix), data(authScheme));
182         } else {
183             if (LOG.isDebugEnabled()) {
184                 LOG.debug("Auth scheme {} cannot be cached", authScheme.getClass());
185             }
186         }
187     }
188 
189     @Override
190     @SuppressWarnings("unchecked")
191     public AuthScheme get(final HttpHost host, final String pathPrefix) {
192         Args.notNull(host, "HTTP host");
193         final AuthData authData = this.map.get(key(host.getSchemeName(), host, pathPrefix));
194         if (authData != null) {
195             try {
196                 final AuthScheme authScheme = authData.clazz.newInstance();
197                 ((StateHolder<Object>) authScheme).restore(authData.state);
198                 return authScheme;
199             } catch (final IllegalAccessException | InstantiationException ex) {
200                 if (LOG.isWarnEnabled()) {
201                     LOG.warn("Unexpected error while reading auth scheme state", ex);
202                 }
203             }
204         }
205         return null;
206     }
207 
208     @Override
209     public void remove(final HttpHost host, final String pathPrefix) {
210         Args.notNull(host, "HTTP host");
211         this.map.remove(key(host.getSchemeName(), host, pathPrefix));
212     }
213 
214     @Override
215     public void clear() {
216         this.map.clear();
217     }
218 
219     @Override
220     public String toString() {
221         return this.map.toString();
222     }
223 
224 }