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