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