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 ch.boye.httpclientandroidlib.conn.routing;
29 
30 import java.net.InetAddress;
31 
32 import ch.boye.httpclientandroidlib.HttpHost;
33 import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
34 import ch.boye.httpclientandroidlib.util.Args;
35 import ch.boye.httpclientandroidlib.util.Asserts;
36 import ch.boye.httpclientandroidlib.util.LangUtils;
37 
38 /**
39  * Helps tracking the steps in establishing a route.
40  *
41  * @since 4.0
42  */
43 @NotThreadSafe
44 public final class RouteTracker implements RouteInfo, Cloneable {
45 
46     /** The target host to connect to. */
47     private final HttpHost targetHost;
48 
49     /**
50      * The local address to connect from.
51      * <code>null</code> indicates that the default should be used.
52      */
53     private final InetAddress localAddress;
54 
55     // the attributes above are fixed at construction time
56     // now follow attributes that indicate the established route
57 
58     /** Whether the first hop of the route is established. */
59     private boolean connected;
60 
61     /** The proxy chain, if any. */
62     private HttpHost[] proxyChain;
63 
64     /** Whether the the route is tunnelled end-to-end through proxies. */
65     private TunnelType tunnelled;
66 
67     /** Whether the route is layered over a tunnel. */
68     private LayerType layered;
69 
70     /** Whether the route is secure. */
71     private boolean secure;
72 
73     /**
74      * Creates a new route tracker.
75      * The target and origin need to be specified at creation time.
76      *
77      * @param target    the host to which to route
78      * @param local     the local address to route from, or
79      *                  <code>null</code> for the default
80      */
RouteTracker(final HttpHost target, final InetAddress local)81     public RouteTracker(final HttpHost target, final InetAddress local) {
82         Args.notNull(target, "Target host");
83         this.targetHost   = target;
84         this.localAddress = local;
85         this.tunnelled    = TunnelType.PLAIN;
86         this.layered      = LayerType.PLAIN;
87     }
88 
89     /**
90      * @since 4.2
91      */
reset()92     public void reset() {
93         this.connected = false;
94         this.proxyChain = null;
95         this.tunnelled = TunnelType.PLAIN;
96         this.layered = LayerType.PLAIN;
97         this.secure = false;
98     }
99 
100     /**
101      * Creates a new tracker for the given route.
102      * Only target and origin are taken from the route,
103      * everything else remains to be tracked.
104      *
105      * @param route     the route to track
106      */
RouteTracker(final HttpRoute route)107     public RouteTracker(final HttpRoute route) {
108         this(route.getTargetHost(), route.getLocalAddress());
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(final boolean secure)117     public final void connectTarget(final boolean secure) {
118         Asserts.check(!this.connected, "Already connected");
119         this.connected = true;
120         this.secure = secure;
121     }
122 
123     /**
124      * Tracks connecting to the first proxy.
125      *
126      * @param proxy     the proxy connected to
127      * @param secure    <code>true</code> if the route is secure,
128      *                  <code>false</code> otherwise
129      */
connectProxy(final HttpHost proxy, final boolean secure)130     public final void connectProxy(final HttpHost proxy, final boolean secure) {
131         Args.notNull(proxy, "Proxy host");
132         Asserts.check(!this.connected, "Already connected");
133         this.connected  = true;
134         this.proxyChain = new HttpHost[]{ proxy };
135         this.secure     = secure;
136     }
137 
138     /**
139      * Tracks tunnelling to the target.
140      *
141      * @param secure    <code>true</code> if the route is secure,
142      *                  <code>false</code> otherwise
143      */
tunnelTarget(final boolean secure)144     public final void tunnelTarget(final boolean secure) {
145         Asserts.check(this.connected, "No tunnel unless connected");
146         Asserts.notNull(this.proxyChain, "No tunnel without proxy");
147         this.tunnelled = TunnelType.TUNNELLED;
148         this.secure    = secure;
149     }
150 
151     /**
152      * Tracks tunnelling to a proxy in a proxy chain.
153      * This will extend the tracked proxy chain, but it does not mark
154      * the route as tunnelled. Only end-to-end tunnels are considered there.
155      *
156      * @param proxy     the proxy tunnelled to
157      * @param secure    <code>true</code> if the route is secure,
158      *                  <code>false</code> otherwise
159      */
tunnelProxy(final HttpHost proxy, final boolean secure)160     public final void tunnelProxy(final HttpHost proxy, final boolean secure) {
161         Args.notNull(proxy, "Proxy host");
162         Asserts.check(this.connected, "No tunnel unless connected");
163         Asserts.notNull(this.proxyChain, "No tunnel without proxy");
164         // prepare an extended proxy chain
165         final HttpHost[] proxies = new HttpHost[this.proxyChain.length+1];
166         System.arraycopy(this.proxyChain, 0,
167                          proxies, 0, this.proxyChain.length);
168         proxies[proxies.length-1] = proxy;
169 
170         this.proxyChain = proxies;
171         this.secure     = secure;
172     }
173 
174     /**
175      * Tracks layering a protocol.
176      *
177      * @param secure    <code>true</code> if the route is secure,
178      *                  <code>false</code> otherwise
179      */
layerProtocol(final boolean secure)180     public final void layerProtocol(final boolean secure) {
181         // it is possible to layer a protocol over a direct connection,
182         // although this case is probably not considered elsewhere
183         Asserts.check(this.connected, "No layered protocol unless connected");
184         this.layered = LayerType.LAYERED;
185         this.secure  = secure;
186     }
187 
getTargetHost()188     public final HttpHost getTargetHost() {
189         return this.targetHost;
190     }
191 
getLocalAddress()192     public final InetAddress getLocalAddress() {
193         return this.localAddress;
194     }
195 
getHopCount()196     public final int getHopCount() {
197         int hops = 0;
198         if (this.connected) {
199             if (proxyChain == null) {
200                 hops = 1;
201             } else {
202                 hops = proxyChain.length + 1;
203             }
204         }
205         return hops;
206     }
207 
getHopTarget(final int hop)208     public final HttpHost getHopTarget(final int hop) {
209         Args.notNegative(hop, "Hop index");
210         final int hopcount = getHopCount();
211         Args.check(hop < hopcount, "Hop index exceeds tracked route length");
212         HttpHost result = null;
213         if (hop < hopcount-1) {
214             result = this.proxyChain[hop];
215         } else {
216             result = this.targetHost;
217         }
218 
219         return result;
220     }
221 
222     public final HttpHost getProxyHost() {
223         return (this.proxyChain == null) ? null : this.proxyChain[0];
224     }
225 
226     public final boolean isConnected() {
227         return this.connected;
228     }
229 
230     public final TunnelType getTunnelType() {
231         return this.tunnelled;
232     }
233 
234     public final boolean isTunnelled() {
235         return (this.tunnelled == TunnelType.TUNNELLED);
236     }
237 
238     public final LayerType getLayerType() {
239         return this.layered;
240     }
241 
242     public final boolean isLayered() {
243         return (this.layered == LayerType.LAYERED);
244     }
245 
246     public final boolean isSecure() {
247         return this.secure;
248     }
249 
250     /**
251      * Obtains the tracked route.
252      * If a route has been tracked, it is {@link #isConnected connected}.
253      * If not connected, nothing has been tracked so far.
254      *
255      * @return  the tracked route, or
256      *          <code>null</code> if nothing has been tracked so far
257      */
258     public final HttpRoute toRoute() {
259         return !this.connected ?
260             null : new HttpRoute(this.targetHost, this.localAddress,
261                                  this.proxyChain, this.secure,
262                                  this.tunnelled, this.layered);
263     }
264 
265     /**
266      * Compares this tracked route to another.
267      *
268      * @param o         the object to compare with
269      *
270      * @return  <code>true</code> if the argument is the same tracked route,
271      *          <code>false</code>
272      */
273     @Override
274     public final boolean equals(final Object o) {
275         if (o == this) {
276             return true;
277         }
278         if (!(o instanceof RouteTracker)) {
279             return false;
280         }
281 
282         final RouteTracker that = (RouteTracker) o;
283         return
284             // Do the cheapest checks first
285             (this.connected == that.connected) &&
286             (this.secure    == that.secure) &&
287             (this.tunnelled == that.tunnelled) &&
288             (this.layered   == that.layered) &&
289             LangUtils.equals(this.targetHost, that.targetHost) &&
290             LangUtils.equals(this.localAddress, that.localAddress) &&
291             LangUtils.equals(this.proxyChain, that.proxyChain);
292     }
293 
294     /**
295      * Generates a hash code for this tracked route.
296      * Route trackers are modifiable and should therefore not be used
297      * as lookup keys. Use {@link #toRoute toRoute} to obtain an
298      * unmodifiable representation of the tracked route.
299      *
300      * @return  the hash code
301      */
302     @Override
303     public final int hashCode() {
304         int hash = LangUtils.HASH_SEED;
305         hash = LangUtils.hashCode(hash, this.targetHost);
306         hash = LangUtils.hashCode(hash, this.localAddress);
307         if (this.proxyChain != null) {
308             for (final HttpHost element : this.proxyChain) {
309                 hash = LangUtils.hashCode(hash, element);
310             }
311         }
312         hash = LangUtils.hashCode(hash, this.connected);
313         hash = LangUtils.hashCode(hash, this.secure);
314         hash = LangUtils.hashCode(hash, this.tunnelled);
315         hash = LangUtils.hashCode(hash, this.layered);
316         return hash;
317     }
318 
319     /**
320      * Obtains a description of the tracked route.
321      *
322      * @return  a human-readable representation of the tracked route
323      */
324     @Override
325     public final String toString() {
326         final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
327 
328         cab.append("RouteTracker[");
329         if (this.localAddress != null) {
330             cab.append(this.localAddress);
331             cab.append("->");
332         }
333         cab.append('{');
334         if (this.connected) {
335             cab.append('c');
336         }
337         if (this.tunnelled == TunnelType.TUNNELLED) {
338             cab.append('t');
339         }
340         if (this.layered == LayerType.LAYERED) {
341             cab.append('l');
342         }
343         if (this.secure) {
344             cab.append('s');
345         }
346         cab.append("}->");
347         if (this.proxyChain != null) {
348             for (final HttpHost element : this.proxyChain) {
349                 cab.append(element);
350                 cab.append("->");
351             }
352         }
353         cab.append(this.targetHost);
354         cab.append(']');
355 
356         return cab.toString();
357     }
358 
359 
360     // default implementation of clone() is sufficient
361     @Override
362     public Object clone() throws CloneNotSupportedException {
363         return super.clone();
364     }
365 
366 }
367