001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.conn.routing; 029 030import java.net.InetAddress; 031 032import org.apache.http.HttpHost; 033import org.apache.http.util.Args; 034import org.apache.http.util.Asserts; 035import org.apache.http.util.LangUtils; 036 037/** 038 * Helps tracking the steps in establishing a route. 039 * 040 * @since 4.0 041 */ 042public final class RouteTracker implements RouteInfo, Cloneable { 043 044 /** The target host to connect to. */ 045 private final HttpHost targetHost; 046 047 /** 048 * The local address to connect from. 049 * {@code null} indicates that the default should be used. 050 */ 051 private final InetAddress localAddress; 052 053 // the attributes above are fixed at construction time 054 // now follow attributes that indicate the established route 055 056 /** Whether the first hop of the route is established. */ 057 private boolean connected; 058 059 /** The proxy chain, if any. */ 060 private HttpHost[] proxyChain; 061 062 /** Whether the the route is tunnelled end-to-end through proxies. */ 063 private TunnelType tunnelled; 064 065 /** Whether the route is layered over a tunnel. */ 066 private LayerType layered; 067 068 /** Whether the route is secure. */ 069 private boolean secure; 070 071 /** 072 * Creates a new route tracker. 073 * The target and origin need to be specified at creation time. 074 * 075 * @param target the host to which to route 076 * @param local the local address to route from, or 077 * {@code null} for the default 078 */ 079 public RouteTracker(final HttpHost target, final InetAddress local) { 080 Args.notNull(target, "Target host"); 081 this.targetHost = target; 082 this.localAddress = local; 083 this.tunnelled = TunnelType.PLAIN; 084 this.layered = LayerType.PLAIN; 085 } 086 087 /** 088 * @since 4.2 089 */ 090 public void reset() { 091 this.connected = false; 092 this.proxyChain = null; 093 this.tunnelled = TunnelType.PLAIN; 094 this.layered = LayerType.PLAIN; 095 this.secure = false; 096 } 097 098 /** 099 * Creates a new tracker for the given route. 100 * Only target and origin are taken from the route, 101 * everything else remains to be tracked. 102 * 103 * @param route the route to track 104 */ 105 public RouteTracker(final HttpRoute route) { 106 this(route.getTargetHost(), route.getLocalAddress()); 107 } 108 109 /** 110 * Tracks connecting to the target. 111 * 112 * @param secure {@code true} if the route is secure, 113 * {@code false} otherwise 114 */ 115 public final void connectTarget(final boolean secure) { 116 Asserts.check(!this.connected, "Already connected"); 117 this.connected = true; 118 this.secure = secure; 119 } 120 121 /** 122 * Tracks connecting to the first proxy. 123 * 124 * @param proxy the proxy connected to 125 * @param secure {@code true} if the route is secure, 126 * {@code false} otherwise 127 */ 128 public final void connectProxy(final HttpHost proxy, final boolean secure) { 129 Args.notNull(proxy, "Proxy host"); 130 Asserts.check(!this.connected, "Already connected"); 131 this.connected = true; 132 this.proxyChain = new HttpHost[]{ proxy }; 133 this.secure = secure; 134 } 135 136 /** 137 * Tracks tunnelling to the target. 138 * 139 * @param secure {@code true} if the route is secure, 140 * {@code false} otherwise 141 */ 142 public final void tunnelTarget(final boolean secure) { 143 Asserts.check(this.connected, "No tunnel unless connected"); 144 Asserts.notNull(this.proxyChain, "No tunnel without proxy"); 145 this.tunnelled = TunnelType.TUNNELLED; 146 this.secure = secure; 147 } 148 149 /** 150 * Tracks tunnelling to a proxy in a proxy chain. 151 * This will extend the tracked proxy chain, but it does not mark 152 * the route as tunnelled. Only end-to-end tunnels are considered there. 153 * 154 * @param proxy the proxy tunnelled to 155 * @param secure {@code true} if the route is secure, 156 * {@code false} otherwise 157 */ 158 public final void tunnelProxy(final HttpHost proxy, final boolean secure) { 159 Args.notNull(proxy, "Proxy host"); 160 Asserts.check(this.connected, "No tunnel unless connected"); 161 Asserts.notNull(this.proxyChain, "No tunnel without proxy"); 162 // prepare an extended proxy chain 163 final HttpHost[] proxies = new HttpHost[this.proxyChain.length+1]; 164 System.arraycopy(this.proxyChain, 0, 165 proxies, 0, this.proxyChain.length); 166 proxies[proxies.length-1] = proxy; 167 168 this.proxyChain = proxies; 169 this.secure = secure; 170 } 171 172 /** 173 * Tracks layering a protocol. 174 * 175 * @param secure {@code true} if the route is secure, 176 * {@code false} otherwise 177 */ 178 public final void layerProtocol(final boolean secure) { 179 // it is possible to layer a protocol over a direct connection, 180 // although this case is probably not considered elsewhere 181 Asserts.check(this.connected, "No layered protocol unless connected"); 182 this.layered = LayerType.LAYERED; 183 this.secure = secure; 184 } 185 186 @Override 187 public final HttpHost getTargetHost() { 188 return this.targetHost; 189 } 190 191 @Override 192 public final InetAddress getLocalAddress() { 193 return this.localAddress; 194 } 195 196 @Override 197 public final int getHopCount() { 198 int hops = 0; 199 if (this.connected) { 200 if (proxyChain == null) { 201 hops = 1; 202 } else { 203 hops = proxyChain.length + 1; 204 } 205 } 206 return hops; 207 } 208 209 @Override 210 public final HttpHost getHopTarget(final int hop) { 211 Args.notNegative(hop, "Hop index"); 212 final int hopcount = getHopCount(); 213 Args.check(hop < hopcount, "Hop index exceeds tracked route length"); 214 HttpHost result = null; 215 if (hop < hopcount-1) { 216 result = this.proxyChain[hop]; 217 } else { 218 result = this.targetHost; 219 } 220 221 return result; 222 } 223 224 @Override 225 public final HttpHost getProxyHost() { 226 return (this.proxyChain == null) ? null : this.proxyChain[0]; 227 } 228 229 public final boolean isConnected() { 230 return this.connected; 231 } 232 233 @Override 234 public final TunnelType getTunnelType() { 235 return this.tunnelled; 236 } 237 238 @Override 239 public final boolean isTunnelled() { 240 return (this.tunnelled == TunnelType.TUNNELLED); 241 } 242 243 @Override 244 public final LayerType getLayerType() { 245 return this.layered; 246 } 247 248 @Override 249 public final boolean isLayered() { 250 return (this.layered == LayerType.LAYERED); 251 } 252 253 @Override 254 public final boolean isSecure() { 255 return this.secure; 256 } 257 258 /** 259 * Obtains the tracked route. 260 * If a route has been tracked, it is {@link #isConnected connected}. 261 * If not connected, nothing has been tracked so far. 262 * 263 * @return the tracked route, or 264 * {@code null} if nothing has been tracked so far 265 */ 266 public final HttpRoute toRoute() { 267 return !this.connected ? 268 null : new HttpRoute(this.targetHost, this.localAddress, 269 this.proxyChain, this.secure, 270 this.tunnelled, this.layered); 271 } 272 273 /** 274 * Compares this tracked route to another. 275 * 276 * @param o the object to compare with 277 * 278 * @return {@code true} if the argument is the same tracked route, 279 * {@code false} 280 */ 281 @Override 282 public final boolean equals(final Object o) { 283 if (o == this) { 284 return true; 285 } 286 if (!(o instanceof RouteTracker)) { 287 return false; 288 } 289 290 final RouteTracker that = (RouteTracker) o; 291 return 292 // Do the cheapest checks first 293 (this.connected == that.connected) && 294 (this.secure == that.secure) && 295 (this.tunnelled == that.tunnelled) && 296 (this.layered == that.layered) && 297 LangUtils.equals(this.targetHost, that.targetHost) && 298 LangUtils.equals(this.localAddress, that.localAddress) && 299 LangUtils.equals(this.proxyChain, that.proxyChain); 300 } 301 302 /** 303 * Generates a hash code for this tracked route. 304 * Route trackers are modifiable and should therefore not be used 305 * as lookup keys. Use {@link #toRoute toRoute} to obtain an 306 * unmodifiable representation of the tracked route. 307 * 308 * @return the hash code 309 */ 310 @Override 311 public final int hashCode() { 312 int hash = LangUtils.HASH_SEED; 313 hash = LangUtils.hashCode(hash, this.targetHost); 314 hash = LangUtils.hashCode(hash, this.localAddress); 315 if (this.proxyChain != null) { 316 for (final HttpHost element : this.proxyChain) { 317 hash = LangUtils.hashCode(hash, element); 318 } 319 } 320 hash = LangUtils.hashCode(hash, this.connected); 321 hash = LangUtils.hashCode(hash, this.secure); 322 hash = LangUtils.hashCode(hash, this.tunnelled); 323 hash = LangUtils.hashCode(hash, this.layered); 324 return hash; 325 } 326 327 /** 328 * Obtains a description of the tracked route. 329 * 330 * @return a human-readable representation of the tracked route 331 */ 332 @Override 333 public final String toString() { 334 final StringBuilder cab = new StringBuilder(50 + getHopCount()*30); 335 336 cab.append("RouteTracker["); 337 if (this.localAddress != null) { 338 cab.append(this.localAddress); 339 cab.append("->"); 340 } 341 cab.append('{'); 342 if (this.connected) { 343 cab.append('c'); 344 } 345 if (this.tunnelled == TunnelType.TUNNELLED) { 346 cab.append('t'); 347 } 348 if (this.layered == LayerType.LAYERED) { 349 cab.append('l'); 350 } 351 if (this.secure) { 352 cab.append('s'); 353 } 354 cab.append("}->"); 355 if (this.proxyChain != null) { 356 for (final HttpHost element : this.proxyChain) { 357 cab.append(element); 358 cab.append("->"); 359 } 360 } 361 cab.append(this.targetHost); 362 cab.append(']'); 363 364 return cab.toString(); 365 } 366 367 368 // default implementation of clone() is sufficient 369 @Override 370 public Object clone() throws CloneNotSupportedException { 371 return super.clone(); 372 } 373 374}