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}