View Javadoc

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 org.apache.http.conn.routing;
29  
30  import java.net.InetAddress;
31  
32  import org.apache.http.HttpHost;
33  import org.apache.http.annotation.NotThreadSafe;
34  import org.apache.http.util.Args;
35  import org.apache.http.util.Asserts;
36  import org.apache.http.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} 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} for the default
80       */
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       */
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      */
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} if the route is secure,
115      *                  {@code false} otherwise
116      */
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} if the route is secure,
128      *                  {@code false} otherwise
129      */
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} if the route is secure,
142      *                  {@code false} otherwise
143      */
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} if the route is secure,
158      *                  {@code false} otherwise
159      */
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} if the route is secure,
178      *                  {@code false} otherwise
179      */
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 
188     @Override
189     public final HttpHost getTargetHost() {
190         return this.targetHost;
191     }
192 
193     @Override
194     public final InetAddress getLocalAddress() {
195         return this.localAddress;
196     }
197 
198     @Override
199     public final int getHopCount() {
200         int hops = 0;
201         if (this.connected) {
202             if (proxyChain == null) {
203                 hops = 1;
204             } else {
205                 hops = proxyChain.length + 1;
206             }
207         }
208         return hops;
209     }
210 
211     @Override
212     public final HttpHost getHopTarget(final int hop) {
213         Args.notNegative(hop, "Hop index");
214         final int hopcount = getHopCount();
215         Args.check(hop < hopcount, "Hop index exceeds tracked route length");
216         HttpHost result = null;
217         if (hop < hopcount-1) {
218             result = this.proxyChain[hop];
219         } else {
220             result = this.targetHost;
221         }
222 
223         return result;
224     }
225 
226     @Override
227     public final HttpHost getProxyHost() {
228         return (this.proxyChain == null) ? null : this.proxyChain[0];
229     }
230 
231     public final boolean isConnected() {
232         return this.connected;
233     }
234 
235     @Override
236     public final TunnelType getTunnelType() {
237         return this.tunnelled;
238     }
239 
240     @Override
241     public final boolean isTunnelled() {
242         return (this.tunnelled == TunnelType.TUNNELLED);
243     }
244 
245     @Override
246     public final LayerType getLayerType() {
247         return this.layered;
248     }
249 
250     @Override
251     public final boolean isLayered() {
252         return (this.layered == LayerType.LAYERED);
253     }
254 
255     @Override
256     public final boolean isSecure() {
257         return this.secure;
258     }
259 
260     /**
261      * Obtains the tracked route.
262      * If a route has been tracked, it is {@link #isConnected connected}.
263      * If not connected, nothing has been tracked so far.
264      *
265      * @return  the tracked route, or
266      *          {@code null} if nothing has been tracked so far
267      */
268     public final HttpRoute toRoute() {
269         return !this.connected ?
270             null : new HttpRoute(this.targetHost, this.localAddress,
271                                  this.proxyChain, this.secure,
272                                  this.tunnelled, this.layered);
273     }
274 
275     /**
276      * Compares this tracked route to another.
277      *
278      * @param o         the object to compare with
279      *
280      * @return  {@code true} if the argument is the same tracked route,
281      *          {@code false}
282      */
283     @Override
284     public final boolean equals(final Object o) {
285         if (o == this) {
286             return true;
287         }
288         if (!(o instanceof RouteTracker)) {
289             return false;
290         }
291 
292         final RouteTracker that = (RouteTracker) o;
293         return
294             // Do the cheapest checks first
295             (this.connected == that.connected) &&
296             (this.secure    == that.secure) &&
297             (this.tunnelled == that.tunnelled) &&
298             (this.layered   == that.layered) &&
299             LangUtils.equals(this.targetHost, that.targetHost) &&
300             LangUtils.equals(this.localAddress, that.localAddress) &&
301             LangUtils.equals(this.proxyChain, that.proxyChain);
302     }
303 
304     /**
305      * Generates a hash code for this tracked route.
306      * Route trackers are modifiable and should therefore not be used
307      * as lookup keys. Use {@link #toRoute toRoute} to obtain an
308      * unmodifiable representation of the tracked route.
309      *
310      * @return  the hash code
311      */
312     @Override
313     public final int hashCode() {
314         int hash = LangUtils.HASH_SEED;
315         hash = LangUtils.hashCode(hash, this.targetHost);
316         hash = LangUtils.hashCode(hash, this.localAddress);
317         if (this.proxyChain != null) {
318             for (final HttpHost element : this.proxyChain) {
319                 hash = LangUtils.hashCode(hash, element);
320             }
321         }
322         hash = LangUtils.hashCode(hash, this.connected);
323         hash = LangUtils.hashCode(hash, this.secure);
324         hash = LangUtils.hashCode(hash, this.tunnelled);
325         hash = LangUtils.hashCode(hash, this.layered);
326         return hash;
327     }
328 
329     /**
330      * Obtains a description of the tracked route.
331      *
332      * @return  a human-readable representation of the tracked route
333      */
334     @Override
335     public final String toString() {
336         final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
337 
338         cab.append("RouteTracker[");
339         if (this.localAddress != null) {
340             cab.append(this.localAddress);
341             cab.append("->");
342         }
343         cab.append('{');
344         if (this.connected) {
345             cab.append('c');
346         }
347         if (this.tunnelled == TunnelType.TUNNELLED) {
348             cab.append('t');
349         }
350         if (this.layered == LayerType.LAYERED) {
351             cab.append('l');
352         }
353         if (this.secure) {
354             cab.append('s');
355         }
356         cab.append("}->");
357         if (this.proxyChain != null) {
358             for (final HttpHost element : this.proxyChain) {
359                 cab.append(element);
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 }