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