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.annotation.NotThreadSafe;
33  import org.apache.http.util.LangUtils;
34  
35  import org.apache.http.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       * @since 4.2
92       */
93      public void reset() {
94          this.connected = false;
95          this.proxyChain = null;
96          this.tunnelled = TunnelType.PLAIN;
97          this.layered = LayerType.PLAIN;
98          this.secure = false;
99      }
100 
101     /**
102      * Creates a new tracker for the given route.
103      * Only target and origin are taken from the route,
104      * everything else remains to be tracked.
105      *
106      * @param route     the route to track
107      */
108     public RouteTracker(HttpRoute route) {
109         this(route.getTargetHost(), route.getLocalAddress());
110     }
111 
112     /**
113      * Tracks connecting to the target.
114      *
115      * @param secure    <code>true</code> if the route is secure,
116      *                  <code>false</code> otherwise
117      */
118     public final void connectTarget(boolean secure) {
119         if (this.connected) {
120             throw new IllegalStateException("Already connected.");
121         }
122         this.connected = true;
123         this.secure = secure;
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      */
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      * Tracks tunnelling to the target.
147      *
148      * @param secure    <code>true</code> if the route is secure,
149      *                  <code>false</code> otherwise
150      */
151     public final void tunnelTarget(boolean secure) {
152         if (!this.connected) {
153             throw new IllegalStateException("No tunnel unless connected.");
154         }
155         if (this.proxyChain == null) {
156             throw new IllegalStateException("No tunnel without proxy.");
157         }
158         this.tunnelled = TunnelType.TUNNELLED;
159         this.secure    = secure;
160     }
161 
162     /**
163      * Tracks tunnelling to a proxy in a proxy chain.
164      * This will extend the tracked proxy chain, but it does not mark
165      * the route as tunnelled. Only end-to-end tunnels are considered there.
166      *
167      * @param proxy     the proxy tunnelled to
168      * @param secure    <code>true</code> if the route is secure,
169      *                  <code>false</code> otherwise
170      */
171     public final void tunnelProxy(HttpHost proxy, boolean secure) {
172         if (proxy == null) {
173             throw new IllegalArgumentException("Proxy host may not be null.");
174         }
175         if (!this.connected) {
176             throw new IllegalStateException("No tunnel unless connected.");
177         }
178         if (this.proxyChain == null) {
179             throw new IllegalStateException("No proxy tunnel without proxy.");
180         }
181 
182         // prepare an extended proxy chain
183         HttpHost[] proxies = new HttpHost[this.proxyChain.length+1];
184         System.arraycopy(this.proxyChain, 0,
185                          proxies, 0, this.proxyChain.length);
186         proxies[proxies.length-1] = proxy;
187 
188         this.proxyChain = proxies;
189         this.secure     = secure;
190     }
191 
192     /**
193      * Tracks layering a protocol.
194      *
195      * @param secure    <code>true</code> if the route is secure,
196      *                  <code>false</code> otherwise
197      */
198     public final void layerProtocol(boolean secure) {
199         // it is possible to layer a protocol over a direct connection,
200         // although this case is probably not considered elsewhere
201         if (!this.connected) {
202             throw new IllegalStateException
203                 ("No layered protocol unless connected.");
204         }
205         this.layered = LayerType.LAYERED;
206         this.secure  = secure;
207     }
208 
209     public final HttpHost getTargetHost() {
210         return this.targetHost;
211     }
212 
213     public final InetAddress getLocalAddress() {
214         return this.localAddress;
215     }
216 
217     public final int getHopCount() {
218         int hops = 0;
219         if (this.connected) {
220             if (proxyChain == null)
221                 hops = 1;
222             else
223                 hops = proxyChain.length + 1;
224         }
225         return hops;
226     }
227 
228     public final HttpHost getHopTarget(int hop) {
229         if (hop < 0)
230             throw new IllegalArgumentException
231                 ("Hop index must not be negative: " + hop);
232         final int hopcount = getHopCount();
233         if (hop >= hopcount) {
234             throw new IllegalArgumentException
235                 ("Hop index " + hop +
236                  " exceeds tracked route length " + hopcount +".");
237         }
238 
239         HttpHost result = null;
240         if (hop < hopcount-1)
241             result = this.proxyChain[hop];
242         else
243             result = this.targetHost;
244 
245         return result;
246     }
247 
248     public final HttpHost getProxyHost() {
249         return (this.proxyChain == null) ? null : this.proxyChain[0];
250     }
251 
252     public final boolean isConnected() {
253         return this.connected;
254     }
255 
256     public final TunnelType getTunnelType() {
257         return this.tunnelled;
258     }
259 
260     public final boolean isTunnelled() {
261         return (this.tunnelled == TunnelType.TUNNELLED);
262     }
263 
264     public final LayerType getLayerType() {
265         return this.layered;
266     }
267 
268     public final boolean isLayered() {
269         return (this.layered == LayerType.LAYERED);
270     }
271 
272     public final boolean isSecure() {
273         return this.secure;
274     }
275 
276     /**
277      * Obtains the tracked route.
278      * If a route has been tracked, it is {@link #isConnected connected}.
279      * If not connected, nothing has been tracked so far.
280      *
281      * @return  the tracked route, or
282      *          <code>null</code> if nothing has been tracked so far
283      */
284     public final HttpRoute toRoute() {
285         return !this.connected ?
286             null : new HttpRoute(this.targetHost, this.localAddress,
287                                  this.proxyChain, this.secure,
288                                  this.tunnelled, this.layered);
289     }
290 
291     /**
292      * Compares this tracked route to another.
293      *
294      * @param o         the object to compare with
295      *
296      * @return  <code>true</code> if the argument is the same tracked route,
297      *          <code>false</code>
298      */
299     @Override
300     public final boolean equals(Object o) {
301         if (o == this)
302             return true;
303         if (!(o instanceof RouteTracker))
304             return false;
305 
306         RouteTracker that = (RouteTracker) o;
307         return
308             // Do the cheapest checks first
309             (this.connected == that.connected) &&
310             (this.secure    == that.secure) &&
311             (this.tunnelled == that.tunnelled) &&
312             (this.layered   == that.layered) &&
313             LangUtils.equals(this.targetHost, that.targetHost) &&
314             LangUtils.equals(this.localAddress, that.localAddress) &&
315             LangUtils.equals(this.proxyChain, that.proxyChain);
316     }
317 
318     /**
319      * Generates a hash code for this tracked route.
320      * Route trackers are modifiable and should therefore not be used
321      * as lookup keys. Use {@link #toRoute toRoute} to obtain an
322      * unmodifiable representation of the tracked route.
323      *
324      * @return  the hash code
325      */
326     @Override
327     public final int hashCode() {
328         int hash = LangUtils.HASH_SEED;
329         hash = LangUtils.hashCode(hash, this.targetHost);
330         hash = LangUtils.hashCode(hash, this.localAddress);
331         if (this.proxyChain != null) {
332             for (int i = 0; i < this.proxyChain.length; i++) {
333                 hash = LangUtils.hashCode(hash, this.proxyChain[i]);
334             }
335         }
336         hash = LangUtils.hashCode(hash, this.connected);
337         hash = LangUtils.hashCode(hash, this.secure);
338         hash = LangUtils.hashCode(hash, this.tunnelled);
339         hash = LangUtils.hashCode(hash, this.layered);
340         return hash;
341     }
342 
343     /**
344      * Obtains a description of the tracked route.
345      *
346      * @return  a human-readable representation of the tracked route
347      */
348     @Override
349     public final String toString() {
350         StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
351 
352         cab.append("RouteTracker[");
353         if (this.localAddress != null) {
354             cab.append(this.localAddress);
355             cab.append("->");
356         }
357         cab.append('{');
358         if (this.connected)
359             cab.append('c');
360         if (this.tunnelled == TunnelType.TUNNELLED)
361             cab.append('t');
362         if (this.layered == LayerType.LAYERED)
363             cab.append('l');
364         if (this.secure)
365             cab.append('s');
366         cab.append("}->");
367         if (this.proxyChain != null) {
368             for (int i=0; i<this.proxyChain.length; i++) {
369                 cab.append(this.proxyChain[i]);
370                 cab.append("->");
371             }
372         }
373         cab.append(this.targetHost);
374         cab.append(']');
375 
376         return cab.toString();
377     }
378 
379 
380     // default implementation of clone() is sufficient
381     @Override
382     public Object clone() throws CloneNotSupportedException {
383         return super.clone();
384     }
385 
386 }