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.io.Serializable;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.Objects;
33  import java.util.concurrent.ConcurrentHashMap;
34  
35  import org.apache.hc.client5.http.SchemePortResolver;
36  import org.apache.hc.client5.http.auth.AuthCache;
37  import org.apache.hc.client5.http.auth.AuthScheme;
38  import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
39  import org.apache.hc.client5.http.impl.StateHolder;
40  import org.apache.hc.core5.annotation.Contract;
41  import org.apache.hc.core5.annotation.ThreadingBehavior;
42  import org.apache.hc.core5.http.HttpHost;
43  import org.apache.hc.core5.net.NamedEndpoint;
44  import org.apache.hc.core5.util.Args;
45  import org.apache.hc.core5.util.LangUtils;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  /**
50   * Default implementation of {@link AuthCache}. This implements
51   * expects {@link AuthScheme} to be {@link Serializable}
52   * in order to be cacheable.
53   * <p>
54   * Instances of this class are thread safe as of version 4.4.
55   * </p>
56   *
57   * @since 4.1
58   */
59  @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
60  public class BasicAuthCache implements AuthCache {
61  
62      private static final Logger LOG = LoggerFactory.getLogger(BasicAuthCache.class);
63  
64      static class Key {
65  
66          final String scheme;
67          final String host;
68          final int port;
69          final String pathPrefix;
70  
71          Key(final String scheme, final String host, final int port, final String pathPrefix) {
72              Args.notBlank(scheme, "Scheme");
73              Args.notBlank(host, "Scheme");
74              this.scheme = scheme.toLowerCase(Locale.ROOT);
75              this.host = host.toLowerCase(Locale.ROOT);
76              this.port = port;
77              this.pathPrefix = pathPrefix;
78          }
79  
80          @Override
81          public boolean equals(final Object obj) {
82              if (this == obj) {
83                  return true;
84              }
85              if (obj instanceof Key) {
86                  final Key that = (Key) obj;
87                  return this.scheme.equals(that.scheme) &&
88                          this.host.equals(that.host) &&
89                          this.port == that.port &&
90                          Objects.equals(this.pathPrefix, that.pathPrefix);
91              }
92              return false;
93          }
94  
95          @Override
96          public int hashCode() {
97              int hash = LangUtils.HASH_SEED;
98              hash = LangUtils.hashCode(hash, this.scheme);
99              hash = LangUtils.hashCode(hash, this.host);
100             hash = LangUtils.hashCode(hash, this.port);
101             hash = LangUtils.hashCode(hash, this.pathPrefix);
102             return hash;
103         }
104 
105         @Override
106         public String toString() {
107             final StringBuilder buf = new StringBuilder();
108             buf.append(scheme).append("://").append(host);
109             if (port >= 0) {
110                 buf.append(":").append(port);
111             }
112             if (pathPrefix != null) {
113                 if (!pathPrefix.startsWith("/")) {
114                     buf.append("/");
115                 }
116                 buf.append(pathPrefix);
117             }
118             return buf.toString();
119         }
120     }
121 
122     static class AuthData {
123 
124         final Class<? extends AuthScheme> clazz;
125         final Object state;
126 
127         public AuthData(final Class<? extends AuthScheme> clazz, final Object state) {
128             this.clazz = clazz;
129             this.state = state;
130         }
131 
132     }
133 
134     private final Map<Key, AuthData> map;
135     private final SchemePortResolver schemePortResolver;
136 
137     /**
138      * Default constructor.
139      *
140      * @since 4.3
141      */
142     public BasicAuthCache(final SchemePortResolver schemePortResolver) {
143         super();
144         this.map = new ConcurrentHashMap<>();
145         this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
146     }
147 
148     public BasicAuthCache() {
149         this(null);
150     }
151 
152     private Key key(final String scheme, final NamedEndpoint authority, final String pathPrefix) {
153         return new Key(scheme, authority.getHostName(), schemePortResolver.resolve(scheme, authority), pathPrefix);
154     }
155 
156     private AuthData data(final AuthScheme authScheme) {
157         return new AuthData(authScheme.getClass(), ((StateHolder) authScheme).store());
158     }
159 
160     @Override
161     public void put(final HttpHost host, final AuthScheme authScheme) {
162         put(host, null, authScheme);
163     }
164 
165     @Override
166     public AuthScheme get(final HttpHost host) {
167         return get(host, null);
168     }
169 
170     @Override
171     public void remove(final HttpHost host) {
172         remove(host, null);
173     }
174 
175     @Override
176     public void put(final HttpHost host, final String pathPrefix, final AuthScheme authScheme) {
177         Args.notNull(host, "HTTP host");
178         if (authScheme == null) {
179             return;
180         }
181         if (authScheme instanceof StateHolder) {
182             this.map.put(key(host.getSchemeName(), host, pathPrefix), data(authScheme));
183         } else {
184             if (LOG.isDebugEnabled()) {
185                 LOG.debug("Auth scheme {} cannot be cached", authScheme.getClass());
186             }
187         }
188     }
189 
190     @Override
191     @SuppressWarnings("unchecked")
192     public AuthScheme get(final HttpHost host, final String pathPrefix) {
193         Args.notNull(host, "HTTP host");
194         final AuthData authData = this.map.get(key(host.getSchemeName(), host, pathPrefix));
195         if (authData != null) {
196             try {
197                 final AuthScheme authScheme = authData.clazz.newInstance();
198                 ((StateHolder<Object>) authScheme).restore(authData.state);
199                 return authScheme;
200             } catch (final IllegalAccessException | InstantiationException ex) {
201                 if (LOG.isWarnEnabled()) {
202                     LOG.warn("Unexpected error while reading auth scheme state", ex);
203                 }
204             }
205         }
206         return null;
207     }
208 
209     @Override
210     public void remove(final HttpHost host, final String pathPrefix) {
211         Args.notNull(host, "HTTP host");
212         this.map.remove(key(host.getSchemeName(), host, pathPrefix));
213     }
214 
215     @Override
216     public void clear() {
217         this.map.clear();
218     }
219 
220     @Override
221     public String toString() {
222         return this.map.toString();
223     }
224 
225 }