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
32package org.apache.http.conn.routing;
33
34import java.net.InetAddress;
35
36import 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 */
50public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
215    public final HttpHost getTargetHost() {
216        return this.targetHost;
217    }
218
219
220    // non-JavaDoc, see interface RouteInfo
221    public final InetAddress getLocalAddress() {
222        return this.localAddress;
223    }
224
225
226    // non-JavaDoc, see interface RouteInfo
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
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
262    public final HttpHost getProxyHost() {
263        return (this.proxyChain == null) ? null : this.proxyChain[0];
264    }
265
266
267    // non-JavaDoc, see interface RouteInfo
268    public final boolean isConnected() {
269        return this.connected;
270    }
271
272
273    // non-JavaDoc, see interface RouteInfo
274    public final TunnelType getTunnelType() {
275        return this.tunnelled;
276    }
277
278
279    // non-JavaDoc, see interface RouteInfo
280    public final boolean isTunnelled() {
281        return (this.tunnelled == TunnelType.TUNNELLED);
282    }
283
284
285    // non-JavaDoc, see interface RouteInfo
286    public final LayerType getLayerType() {
287        return this.layered;
288    }
289
290
291    // non-JavaDoc, see interface RouteInfo
292    public final boolean isLayered() {
293        return (this.layered == LayerType.LAYERED);
294    }
295
296
297    // non-JavaDoc, see interface RouteInfo
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     */
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
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
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
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
434    public Object clone() throws CloneNotSupportedException {
435        return super.clone();
436    }
437
438
439} // class RouteTracker
440