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