• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/routing/RouteTracker.java $
3  * $Revision: 620254 $
4  * $Date: 2008-02-10 02:18:48 -0800 (Sun, 10 Feb 2008) $
5  *
6  * ====================================================================
7  * Licensed to the Apache Software Foundation (ASF) under one
8  * or more contributor license agreements.  See the NOTICE file
9  * distributed with this work for additional information
10  * regarding copyright ownership.  The ASF licenses this file
11  * to you under the Apache License, Version 2.0 (the
12  * "License"); you may not use this file except in compliance
13  * with the License.  You may obtain a copy of the License at
14  *
15  *   http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing,
18  * software distributed under the License is distributed on an
19  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20  * KIND, either express or implied.  See the License for the
21  * specific language governing permissions and limitations
22  * under the License.
23  * ====================================================================
24  *
25  * This software consists of voluntary contributions made by many
26  * individuals on behalf of the Apache Software Foundation.  For more
27  * information on the Apache Software Foundation, please see
28  * <http://www.apache.org/>.
29  *
30  */
31 
32 package org.apache.http.conn.routing;
33 
34 import java.net.InetAddress;
35 
36 import org.apache.http.HttpHost;
37 
38 
39 /**
40  * Helps tracking the steps in establishing a route.
41  *
42  * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
43  *
44  *
45  * <!-- empty lines to avoid svn diff problems -->
46  * @version $Revision: 620254 $
47  *
48  * @since 4.0
49  */
50 public final class RouteTracker implements RouteInfo, Cloneable {
51 
52     /** The target host to connect to. */
53     private final HttpHost targetHost;
54 
55     /**
56      * The local address to connect from.
57      * <code>null</code> indicates that the default should be used.
58      */
59     private final InetAddress localAddress;
60 
61     // the attributes above are fixed at construction time
62     // now follow attributes that indicate the established route
63 
64     /** Whether the first hop of the route is established. */
65     private boolean connected;
66 
67     /** The proxy chain, if any. */
68     private HttpHost[] proxyChain;
69 
70     /** Whether the the route is tunnelled end-to-end through proxies. */
71     private TunnelType tunnelled;
72 
73     /** Whether the route is layered over a tunnel. */
74     private LayerType layered;
75 
76     /** Whether the route is secure. */
77     private boolean secure;
78 
79 
80     /**
81      * Creates a new route tracker.
82      * The target and origin need to be specified at creation time.
83      *
84      * @param target    the host to which to route
85      * @param local     the local address to route from, or
86      *                  <code>null</code> for the default
87      */
RouteTracker(HttpHost target, InetAddress local)88     public RouteTracker(HttpHost target, InetAddress local) {
89         if (target == null) {
90             throw new IllegalArgumentException("Target host may not be null.");
91         }
92         this.targetHost   = target;
93         this.localAddress = local;
94         this.tunnelled    = TunnelType.PLAIN;
95         this.layered      = LayerType.PLAIN;
96     }
97 
98 
99     /**
100      * Creates a new tracker for the given route.
101      * Only target and origin are taken from the route,
102      * everything else remains to be tracked.
103      *
104      * @param route     the route to track
105      */
RouteTracker(HttpRoute route)106     public RouteTracker(HttpRoute route) {
107         this(route.getTargetHost(), route.getLocalAddress());
108     }
109 
110 
111     /**
112      * Tracks connecting to the target.
113      *
114      * @param secure    <code>true</code> if the route is secure,
115      *                  <code>false</code> otherwise
116      */
connectTarget(boolean secure)117     public final void connectTarget(boolean secure) {
118         if (this.connected) {
119             throw new IllegalStateException("Already connected.");
120         }
121         this.connected = true;
122         this.secure = secure;
123     }
124 
125 
126     /**
127      * Tracks connecting to the first proxy.
128      *
129      * @param proxy     the proxy connected to
130      * @param secure    <code>true</code> if the route is secure,
131      *                  <code>false</code> otherwise
132      */
connectProxy(HttpHost proxy, boolean secure)133     public final void connectProxy(HttpHost proxy, boolean secure) {
134         if (proxy == null) {
135             throw new IllegalArgumentException("Proxy host may not be null.");
136         }
137         if (this.connected) {
138             throw new IllegalStateException("Already connected.");
139         }
140         this.connected  = true;
141         this.proxyChain = new HttpHost[]{ proxy };
142         this.secure     = secure;
143     }
144 
145 
146     /**
147      * Tracks tunnelling to the target.
148      *
149      * @param secure    <code>true</code> if the route is secure,
150      *                  <code>false</code> otherwise
151      */
tunnelTarget(boolean secure)152     public final void tunnelTarget(boolean secure) {
153         if (!this.connected) {
154             throw new IllegalStateException("No tunnel unless connected.");
155         }
156         if (this.proxyChain == null) {
157             throw new IllegalStateException("No tunnel without proxy.");
158         }
159         this.tunnelled = TunnelType.TUNNELLED;
160         this.secure    = secure;
161     }
162 
163 
164     /**
165      * Tracks tunnelling to a proxy in a proxy chain.
166      * This will extend the tracked proxy chain, but it does not mark
167      * the route as tunnelled. Only end-to-end tunnels are considered there.
168      *
169      * @param proxy     the proxy tunnelled to
170      * @param secure    <code>true</code> if the route is secure,
171      *                  <code>false</code> otherwise
172      */
tunnelProxy(HttpHost proxy, boolean secure)173     public final void tunnelProxy(HttpHost proxy, boolean secure) {
174         if (proxy == null) {
175             throw new IllegalArgumentException("Proxy host may not be null.");
176         }
177         if (!this.connected) {
178             throw new IllegalStateException("No tunnel unless connected.");
179         }
180         if (this.proxyChain == null) {
181             throw new IllegalStateException("No proxy tunnel without proxy.");
182         }
183 
184         // prepare an extended proxy chain
185         HttpHost[] proxies = new HttpHost[this.proxyChain.length+1];
186         System.arraycopy(this.proxyChain, 0,
187                          proxies, 0, this.proxyChain.length);
188         proxies[proxies.length-1] = proxy;
189 
190         this.proxyChain = proxies;
191         this.secure     = secure;
192     }
193 
194 
195     /**
196      * Tracks layering a protocol.
197      *
198      * @param secure    <code>true</code> if the route is secure,
199      *                  <code>false</code> otherwise
200      */
layerProtocol(boolean secure)201     public final void layerProtocol(boolean secure) {
202         // it is possible to layer a protocol over a direct connection,
203         // although this case is probably not considered elsewhere
204         if (!this.connected) {
205             throw new IllegalStateException
206                 ("No layered protocol unless connected.");
207         }
208         this.layered = LayerType.LAYERED;
209         this.secure  = secure;
210     }
211 
212 
213 
214     // non-JavaDoc, see interface RouteInfo
getTargetHost()215     public final HttpHost getTargetHost() {
216         return this.targetHost;
217     }
218 
219 
220     // non-JavaDoc, see interface RouteInfo
getLocalAddress()221     public final InetAddress getLocalAddress() {
222         return this.localAddress;
223     }
224 
225 
226     // non-JavaDoc, see interface RouteInfo
getHopCount()227     public final int getHopCount() {
228         int hops = 0;
229         if (this.connected) {
230             if (proxyChain == null)
231                 hops = 1;
232             else
233                 hops = proxyChain.length + 1;
234         }
235         return hops;
236     }
237 
238 
239     // non-JavaDoc, see interface RouteInfo
getHopTarget(int hop)240     public final HttpHost getHopTarget(int hop) {
241         if (hop < 0)
242             throw new IllegalArgumentException
243                 ("Hop index must not be negative: " + hop);
244         final int hopcount = getHopCount();
245         if (hop >= hopcount) {
246             throw new IllegalArgumentException
247                 ("Hop index " + hop +
248                  " exceeds tracked route length " + hopcount +".");
249         }
250 
251         HttpHost result = null;
252         if (hop < hopcount-1)
253             result = this.proxyChain[hop];
254         else
255             result = this.targetHost;
256 
257         return result;
258     }
259 
260 
261     // non-JavaDoc, see interface RouteInfo
getProxyHost()262     public final HttpHost getProxyHost() {
263         return (this.proxyChain == null) ? null : this.proxyChain[0];
264     }
265 
266 
267     // non-JavaDoc, see interface RouteInfo
isConnected()268     public final boolean isConnected() {
269         return this.connected;
270     }
271 
272 
273     // non-JavaDoc, see interface RouteInfo
getTunnelType()274     public final TunnelType getTunnelType() {
275         return this.tunnelled;
276     }
277 
278 
279     // non-JavaDoc, see interface RouteInfo
isTunnelled()280     public final boolean isTunnelled() {
281         return (this.tunnelled == TunnelType.TUNNELLED);
282     }
283 
284 
285     // non-JavaDoc, see interface RouteInfo
getLayerType()286     public final LayerType getLayerType() {
287         return this.layered;
288     }
289 
290 
291     // non-JavaDoc, see interface RouteInfo
isLayered()292     public final boolean isLayered() {
293         return (this.layered == LayerType.LAYERED);
294     }
295 
296 
297     // non-JavaDoc, see interface RouteInfo
isSecure()298     public final boolean isSecure() {
299         return this.secure;
300     }
301 
302 
303     /**
304      * Obtains the tracked route.
305      * If a route has been tracked, it is {@link #isConnected connected}.
306      * If not connected, nothing has been tracked so far.
307      *
308      * @return  the tracked route, or
309      *          <code>null</code> if nothing has been tracked so far
310      */
toRoute()311     public final HttpRoute toRoute() {
312         return !this.connected ?
313             null : new HttpRoute(this.targetHost, this.localAddress,
314                                  this.proxyChain, this.secure,
315                                  this.tunnelled, this.layered);
316     }
317 
318 
319     /**
320      * Compares this tracked route to another.
321      *
322      * @param o         the object to compare with
323      *
324      * @return  <code>true</code> if the argument is the same tracked route,
325      *          <code>false</code>
326      */
327     @Override
equals(Object o)328     public final boolean equals(Object o) {
329         if (o == this)
330             return true;
331         if (!(o instanceof RouteTracker))
332             return false;
333 
334         RouteTracker that = (RouteTracker) o;
335         boolean equal = this.targetHost.equals(that.targetHost);
336         equal &=
337             ( this.localAddress == that.localAddress) ||
338             ((this.localAddress != null) &&
339               this.localAddress.equals(that.localAddress));
340         equal &=
341             ( this.proxyChain        == that.proxyChain) ||
342             ((this.proxyChain        != null) &&
343              (that.proxyChain        != null) &&
344              (this.proxyChain.length == that.proxyChain.length));
345         // comparison of actual proxies follows below
346         equal &=
347             (this.connected == that.connected) &&
348             (this.secure    == that.secure) &&
349             (this.tunnelled == that.tunnelled) &&
350             (this.layered   == that.layered);
351 
352         // chain length has been compared above, now check the proxies
353         if (equal && (this.proxyChain != null)) {
354             for (int i=0; equal && (i<this.proxyChain.length); i++)
355                 equal = this.proxyChain[i].equals(that.proxyChain[i]);
356         }
357 
358         return equal;
359     }
360 
361 
362     /**
363      * Generates a hash code for this tracked route.
364      * Route trackers are modifiable and should therefore not be used
365      * as lookup keys. Use {@link #toRoute toRoute} to obtain an
366      * unmodifiable representation of the tracked route.
367      *
368      * @return  the hash code
369      */
370     @Override
hashCode()371     public final int hashCode() {
372 
373         int hc = this.targetHost.hashCode();
374 
375         if (this.localAddress != null)
376             hc ^= localAddress.hashCode();
377         if (this.proxyChain != null) {
378             hc ^= proxyChain.length;
379             for (int i=0; i<proxyChain.length; i++)
380                 hc ^= proxyChain[i].hashCode();
381         }
382 
383         if (this.connected)
384             hc ^= 0x11111111;
385         if (this.secure)
386             hc ^= 0x22222222;
387 
388         hc ^= this.tunnelled.hashCode();
389         hc ^= this.layered.hashCode();
390 
391         return hc;
392     }
393 
394 
395     /**
396      * Obtains a description of the tracked route.
397      *
398      * @return  a human-readable representation of the tracked route
399      */
400     @Override
toString()401     public final String toString() {
402         StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
403 
404         cab.append("RouteTracker[");
405         if (this.localAddress != null) {
406             cab.append(this.localAddress);
407             cab.append("->");
408         }
409         cab.append('{');
410         if (this.connected)
411             cab.append('c');
412         if (this.tunnelled == TunnelType.TUNNELLED)
413             cab.append('t');
414         if (this.layered == LayerType.LAYERED)
415             cab.append('l');
416         if (this.secure)
417             cab.append('s');
418         cab.append("}->");
419         if (this.proxyChain != null) {
420             for (int i=0; i<this.proxyChain.length; i++) {
421                 cab.append(this.proxyChain[i]);
422                 cab.append("->");
423             }
424         }
425         cab.append(this.targetHost);
426         cab.append(']');
427 
428         return cab.toString();
429     }
430 
431 
432     // default implementation of clone() is sufficient
433     @Override
clone()434     public Object clone() throws CloneNotSupportedException {
435         return super.clone();
436     }
437 
438 
439 } // class RouteTracker
440