Home | History | Annotate | Download | only in routing
      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 
     32 package org.apache.http.conn.routing;
     33 
     34 import java.net.InetAddress;
     35 
     36 import 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  */
     50 public 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