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  
28  package org.apache.http.conn.routing;
29  
30  import java.net.InetAddress;
31  
32  import org.apache.http.annotation.Immutable;
33  import org.apache.http.util.LangUtils;
34  
35  import org.apache.http.HttpHost;
36  
37  /**
38   * The route for a request.
39   * Instances of this class are unmodifiable and therefore suitable
40   * for use as lookup keys.
41   *
42   * @since 4.0
43   */
44  @Immutable
45  public final class HttpRoute implements RouteInfo, Cloneable {
46  
47      private static final HttpHost[] EMPTY_HTTP_HOST_ARRAY = new HttpHost[]{};
48  
49      /** The target host to connect to. */
50      private final HttpHost targetHost;
51  
52      /**
53       * The local address to connect from.
54       * <code>null</code> indicates that the default should be used.
55       */
56      private final InetAddress localAddress;
57  
58      /** The proxy servers, if any. Never null. */
59      private final HttpHost[] proxyChain;
60  
61      /** Whether the the route is tunnelled through the proxy. */
62      private final TunnelType tunnelled;
63  
64      /** Whether the route is layered. */
65      private final LayerType layered;
66  
67      /** Whether the route is (supposed to be) secure. */
68      private final boolean secure;
69  
70  
71      /**
72       * Internal, fully-specified constructor.
73       * This constructor does <i>not</i> clone the proxy chain array,
74       * nor test it for <code>null</code> elements. This conversion and
75       * check is the responsibility of the public constructors.
76       * The order of arguments here is different from the similar public
77       * constructor, as required by Java.
78       *
79       * @param local     the local address to route from, or
80       *                  <code>null</code> for the default
81       * @param target    the host to which to route
82       * @param proxies   the proxy chain to use, or
83       *                  <code>null</code> for a direct route
84       * @param secure    <code>true</code> if the route is (to be) secure,
85       *                  <code>false</code> otherwise
86       * @param tunnelled the tunnel type of this route, or
87       *                  <code>null</code> for PLAIN
88       * @param layered   the layering type of this route, or
89       *                  <code>null</code> for PLAIN
90       */
91      private HttpRoute(InetAddress local,
92                        HttpHost target, HttpHost[] proxies,
93                        boolean secure,
94                        TunnelType tunnelled, LayerType layered) {
95          if (target == null) {
96              throw new IllegalArgumentException
97                  ("Target host may not be null.");
98          }
99          if (proxies == null) {
100             throw new IllegalArgumentException
101                 ("Proxies may not be null.");
102         }
103         if ((tunnelled == TunnelType.TUNNELLED) && (proxies.length == 0)) {
104             throw new IllegalArgumentException
105                 ("Proxy required if tunnelled.");
106         }
107 
108         // tunnelled is already checked above, that is in line with the default
109         if (tunnelled == null)
110             tunnelled = TunnelType.PLAIN;
111         if (layered == null)
112             layered = LayerType.PLAIN;
113 
114         this.targetHost   = target;
115         this.localAddress = local;
116         this.proxyChain   = proxies;
117         this.secure       = secure;
118         this.tunnelled    = tunnelled;
119         this.layered      = layered;
120     }
121 
122 
123     /**
124      * Creates a new route with all attributes specified explicitly.
125      *
126      * @param target    the host to which to route
127      * @param local     the local address to route from, or
128      *                  <code>null</code> for the default
129      * @param proxies   the proxy chain to use, or
130      *                  <code>null</code> for a direct route
131      * @param secure    <code>true</code> if the route is (to be) secure,
132      *                  <code>false</code> otherwise
133      * @param tunnelled the tunnel type of this route
134      * @param layered   the layering type of this route
135      */
136     public HttpRoute(HttpHost target, InetAddress local, HttpHost[] proxies,
137                      boolean secure, TunnelType tunnelled, LayerType layered) {
138         this(local, target, toChain(proxies), secure, tunnelled, layered);
139     }
140 
141 
142     /**
143      * Creates a new route with at most one proxy.
144      *
145      * @param target    the host to which to route
146      * @param local     the local address to route from, or
147      *                  <code>null</code> for the default
148      * @param proxy     the proxy to use, or
149      *                  <code>null</code> for a direct route
150      * @param secure    <code>true</code> if the route is (to be) secure,
151      *                  <code>false</code> otherwise
152      * @param tunnelled <code>true</code> if the route is (to be) tunnelled
153      *                  via the proxy,
154      *                  <code>false</code> otherwise
155      * @param layered   <code>true</code> if the route includes a
156      *                  layered protocol,
157      *                  <code>false</code> otherwise
158      */
159     public HttpRoute(HttpHost target, InetAddress local, HttpHost proxy,
160                      boolean secure, TunnelType tunnelled, LayerType layered) {
161         this(local, target, toChain(proxy), secure, tunnelled, layered);
162     }
163 
164 
165     /**
166      * Creates a new direct route.
167      * That is a route without a proxy.
168      *
169      * @param target    the host to which to route
170      * @param local     the local address to route from, or
171      *                  <code>null</code> for the default
172      * @param secure    <code>true</code> if the route is (to be) secure,
173      *                  <code>false</code> otherwise
174      */
175     public HttpRoute(HttpHost target, InetAddress local, boolean secure) {
176         this(local, target, EMPTY_HTTP_HOST_ARRAY, secure, TunnelType.PLAIN, LayerType.PLAIN);
177     }
178 
179 
180     /**
181      * Creates a new direct insecure route.
182      *
183      * @param target    the host to which to route
184      */
185     public HttpRoute(HttpHost target) {
186         this(null, target, EMPTY_HTTP_HOST_ARRAY, false, TunnelType.PLAIN, LayerType.PLAIN);
187     }
188 
189 
190     /**
191      * Creates a new route through a proxy.
192      * When using this constructor, the <code>proxy</code> MUST be given.
193      * For convenience, it is assumed that a secure connection will be
194      * layered over a tunnel through the proxy.
195      *
196      * @param target    the host to which to route
197      * @param local     the local address to route from, or
198      *                  <code>null</code> for the default
199      * @param proxy     the proxy to use
200      * @param secure    <code>true</code> if the route is (to be) secure,
201      *                  <code>false</code> otherwise
202      */
203     public HttpRoute(HttpHost target, InetAddress local, HttpHost proxy,
204                      boolean secure) {
205         this(local, target, toChain(proxy), secure,
206              secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
207              secure ? LayerType.LAYERED    : LayerType.PLAIN);
208         if (proxy == null) {
209             throw new IllegalArgumentException
210                 ("Proxy host may not be null.");
211         }
212     }
213 
214 
215     /**
216      * Helper to convert a proxy to a proxy chain.
217      *
218      * @param proxy     the only proxy in the chain, or <code>null</code>
219      *
220      * @return  a proxy chain array, may be empty (never null)
221      */
222     private static HttpHost[] toChain(HttpHost proxy) {
223         if (proxy == null)
224             return EMPTY_HTTP_HOST_ARRAY;
225 
226         return new HttpHost[]{ proxy };
227     }
228 
229 
230     /**
231      * Helper to duplicate and check a proxy chain.
232      * <code>null</code> is converted to an empty proxy chain.
233      *
234      * @param proxies   the proxy chain to duplicate, or <code>null</code>
235      *
236      * @return  a new proxy chain array, may be empty (never null)
237      */
238     private static HttpHost[] toChain(HttpHost[] proxies) {
239         if ((proxies == null) || (proxies.length < 1))
240             return EMPTY_HTTP_HOST_ARRAY;
241 
242         for (HttpHost proxy : proxies) {
243             if (proxy == null)
244                 throw new IllegalArgumentException
245                         ("Proxy chain may not contain null elements.");
246         }
247 
248         // copy the proxy chain, the traditional way
249         HttpHost[] result = new HttpHost[proxies.length];
250         System.arraycopy(proxies, 0, result, 0, proxies.length);
251 
252         return result;
253     }
254 
255 
256 
257     // non-JavaDoc, see interface RouteInfo
258     public final HttpHost getTargetHost() {
259         return this.targetHost;
260     }
261 
262 
263     // non-JavaDoc, see interface RouteInfo
264     public final InetAddress getLocalAddress() {
265         return this.localAddress;
266     }
267 
268 
269     public final int getHopCount() {
270         return proxyChain.length+1;
271     }
272 
273 
274     public final HttpHost getHopTarget(int hop) {
275         if (hop < 0)
276             throw new IllegalArgumentException
277                 ("Hop index must not be negative: " + hop);
278         final int hopcount = getHopCount();
279         if (hop >= hopcount)
280             throw new IllegalArgumentException
281                 ("Hop index " + hop +
282                  " exceeds route length " + hopcount);
283 
284         HttpHost result = null;
285         if (hop < hopcount-1)
286             result = this.proxyChain[hop];
287         else
288             result = this.targetHost;
289 
290         return result;
291     }
292 
293 
294     public final HttpHost getProxyHost() {
295         return (this.proxyChain.length == 0) ? null : this.proxyChain[0];
296     }
297 
298 
299     public final TunnelType getTunnelType() {
300         return this.tunnelled;
301     }
302 
303 
304     public final boolean isTunnelled() {
305         return (this.tunnelled == TunnelType.TUNNELLED);
306     }
307 
308 
309     public final LayerType getLayerType() {
310         return this.layered;
311     }
312 
313 
314     public final boolean isLayered() {
315         return (this.layered == LayerType.LAYERED);
316     }
317 
318 
319     public final boolean isSecure() {
320         return this.secure;
321     }
322 
323 
324     /**
325      * Compares this route to another.
326      *
327      * @param obj         the object to compare with
328      *
329      * @return  <code>true</code> if the argument is the same route,
330      *          <code>false</code>
331      */
332     @Override
333     public final boolean equals(Object obj) {
334         if (this == obj) return true;
335         if (obj instanceof HttpRoute) {
336             HttpRoute that = (HttpRoute) obj;
337             return
338                 // Do the cheapest tests first
339                 (this.secure    == that.secure) &&
340                 (this.tunnelled == that.tunnelled) &&
341                 (this.layered   == that.layered) &&
342                 LangUtils.equals(this.targetHost, that.targetHost) &&
343                 LangUtils.equals(this.localAddress, that.localAddress) &&
344                 LangUtils.equals(this.proxyChain, that.proxyChain);
345         } else {
346             return false;
347         }
348     }
349 
350 
351     /**
352      * Generates a hash code for this route.
353      *
354      * @return  the hash code
355      */
356     @Override
357     public final int hashCode() {
358         int hash = LangUtils.HASH_SEED;
359         hash = LangUtils.hashCode(hash, this.targetHost);
360         hash = LangUtils.hashCode(hash, this.localAddress);
361         for (int i = 0; i < this.proxyChain.length; i++) {
362             hash = LangUtils.hashCode(hash, this.proxyChain[i]);
363         }
364         hash = LangUtils.hashCode(hash, this.secure);
365         hash = LangUtils.hashCode(hash, this.tunnelled);
366         hash = LangUtils.hashCode(hash, this.layered);
367         return hash;
368     }
369 
370 
371     /**
372      * Obtains a description of this route.
373      *
374      * @return  a human-readable representation of this route
375      */
376     @Override
377     public final String toString() {
378         StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
379         if (this.localAddress != null) {
380             cab.append(this.localAddress);
381             cab.append("->");
382         }
383         cab.append('{');
384         if (this.tunnelled == TunnelType.TUNNELLED)
385             cab.append('t');
386         if (this.layered == LayerType.LAYERED)
387             cab.append('l');
388         if (this.secure)
389             cab.append('s');
390         cab.append("}->");
391         for (HttpHost aProxyChain : this.proxyChain) {
392             cab.append(aProxyChain);
393             cab.append("->");
394         }
395         cab.append(this.targetHost);
396         return cab.toString();
397     }
398 
399 
400     // default implementation of clone() is sufficient
401     @Override
402     public Object clone() throws CloneNotSupportedException {
403         return super.clone();
404     }
405 
406 }