EndpointRouter.java :  » UnTagged » peerdroid » net » jxta » impl » endpoint » router » Android Open Source

Android Open Source » UnTagged » peerdroid 
peerdroid » net » jxta » impl » endpoint » router » EndpointRouter.java
/*
 *
 * $Id: EndpointRouter.java,v 1.2 2005/08/30 22:41:13 hamada Exp $
 *
 * Copyright (c) 2001 Sun Microsystems, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *       Sun Microsystems, Inc. for Project JXTA."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
 *    must not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact Project JXTA at http://www.jxta.org.
 *
 * 5. Products derived from this software may not be called "JXTA",
 *    nor may "JXTA" appear in their name, without prior written
 *    permission of Sun.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL SUN MICROSYSTEMS OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of Project JXTA.  For more
 * information on Project JXTA, please see
 * <http://www.jxta.org/>.
 *
 * This license is based on the BSD license adopted by the Apache Foundation.
 */

package net.jxta.impl.endpoint.router;


import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import java.io.IOException;
import java.net.URISyntaxException;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.Element;
import net.jxta.document.TextElement;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.EndpointListener;
import net.jxta.endpoint.EndpointService;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageReceiver;
import net.jxta.endpoint.MessageSender;
import net.jxta.endpoint.MessageTransport;
import net.jxta.endpoint.Messenger;
import net.jxta.endpoint.MessengerEvent;
import net.jxta.endpoint.MessengerEventListener;
import net.jxta.id.ID;
import net.jxta.id.IDFactory;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.Module;
import net.jxta.protocol.AccessPointAdvertisement;
import net.jxta.protocol.PeerAdvertisement;
import net.jxta.protocol.RouteAdvertisement;

import net.jxta.exception.PeerGroupException;

import net.jxta.impl.endpoint.LoopbackMessenger;
import net.jxta.impl.util.FastHashMap;
import net.jxta.impl.util.TimeUtils;
import net.jxta.impl.util.TimerThreadNamer;

import net.jxta.impl.endpoint.IllegalTransportLoopException;


public class EndpointRouter implements EndpointListener,
            MessageReceiver,
            MessageSender,
            MessengerEventListener,
    Module {

    /**
     *    Log4J Logger
     */
    private static transient final Logger LOG = Logger.getLogger(EndpointRouter.class.getName());

    /**
     *  These are peers which we know multi-hop routes for.
     *
     *  <p/><ul>
     *      <li>Key is peer id as a {@link et.jxta.endpoint.EndpointAddress}</li>
     *      <li>value is a {@link Route}.</li>
     *  </ul>
     */
    private Map routedRoutes = new FastHashMap(16);

    /**
     *  A record of failures.
     *
     *  <p/><ul>
     *      <li>keys are {@link net.jxta.endpoint.EndpointAddress}.</li>
     *      <li>values are the time of failure as {@link java.lang.Long}.</li>
     *  </ul>
     */
    private Map triedAndFailed = new HashMap();

    /**
     * local peer ID as a endpointAddress.
     */
    protected EndpointAddress localPeerAddr = null;

    /**
     * local Peer ID
     */
    private ID localPeerId = null;

    /**
     * endpointservice handle
     *
     */
    EndpointService endpoint = null;

    /**
     * PeerGroup handle
     */
    private PeerGroup group = null;

    /**
     * Until we decide otherwise, the router is *by definition* handling
     * peerID addressed messages.
     */
    private static String routerPName = "jxta";

    /**
     * Router Service Name
     */
    public static final String routerSName = "EndpointRouter";

    /**
     * Whenever we initiate connectivity to a peer (creating a direct route).
     * we remember that we need to send our route adv to that peer. So that
     * it has a chance to re-establish the connection from its side, if need
     * be. The route adv is actually sent piggy-backed on the first message
     * that goes there.
     *
     * <p>Values are {@link net.jxta.endpoint.EndpointAddress}.
     */
    private Set newDestinations = Collections.synchronizedSet(new HashSet());

    /**
     * A pool of messengers categorized by logical address.
     * This actually is the direct routes map.
     */
    private Destinations destinations;

    /**
     *  A record of expiration time of known bad routes we received a NACK route
     *
     *  <p/><ul>
     *      <li>Keys are {@link net.jxta.endpoint.EndpointAddress}.</li>
     *      <li>Values are {@link net.jxta.impl.endpoint.router.BadRoute}.</li>
     *  </ul>
     *
     */
    private Map badRoutes = new HashMap();

    /**
     * We record queries when first started and keep them pending for
     * a while. Threads coming in the meanwhile wait for a result without
     * initiating a query. Thus threads may wait passed the point where
     * the query is no-longer pending, and, although they could initiate
     * a new one, they do not. However, other threads coming later may initiate
     * a new query. So a query is not re-initiated systematically on a fixed schedule.
     * This mechanism also serves to avoid infinite recursions if we're looking
     * for the route to a rendezvous (route queries will try to go there
     * themselves).
     * FIXME: jice@jxta.org 20020903 this is approximate. We can do
     * cleaner/better than that, but it's an inexpensive improvement over what
     * was there before.
     * FIXME: tra@jxta.org 20030818 the pending hashmap should be moved
     * in the routeResolver class as this will allow to only synchronize
     * on the routeResolver object rather than the router object.
     *  <p/><ul>
     *      <li>Keys are {@link net.jxta.endpoint.EndpointAddress}.</li>
     *      <li>Values are {@link ClearPendingQuery}.</li>
     *  </ul>
     */
    protected Map pendingQueries = new HashMap();

    /**
     * Timer by which we schedule the clearing of peinding queries.
     */
    private Timer timer;

    protected class ClearPendingQuery extends TimerTask {
        EndpointAddress pid;
        volatile boolean failed = false;
        long timeToRetry = 0;

        ClearPendingQuery(EndpointAddress pid) {
            this.pid = pid;
            // We schedule for one tick at one minute and another at 5 minutes
            // after the second, we cancel ourselves.
            timer.schedule(this, 1L * TimeUtils.AMINUTE, 5L * TimeUtils.AMINUTE);
            timeToRetry = TimeUtils.toAbsoluteTimeMillis(20L * TimeUtils.ASECOND);
        }

        /**
         *  {@inheritDoc}
         */
        public void run() {
            try {
                if (failed) {
                    // Second tick.
                    // This negative cache info is expired.
                    synchronized (EndpointRouter.this) {
                        pendingQueries.remove(pid);
                    }
                    this.cancel();
                } else {
                    // First timer tick. We're done trying. This is now a negative
                    // cache info. For the next 5 minutes that destination fails
                    // immediately unless it unexpectedly gets finaly resolved.
                    failed = true;
                }
            } catch (Throwable all) {
                if (LOG.isEnabledFor(Level.ERROR)) {
                    LOG.error("Uncaught Throwable in timer task " + Thread.currentThread().getName() + " for " + pid, all);
                }
            }
        }

        public synchronized boolean isTimeToRetry() {
            if (TimeUtils.toRelativeTimeMillis(timeToRetry) > 0) {
                return false;
            }
            // timeToRetry is passed. Set the next time to retry from now.
            timeToRetry = TimeUtils.toAbsoluteTimeMillis(20L * TimeUtils.ASECOND);
            return true;
        }

        public boolean isFailed() {
            return failed;
        }
    }

    /**
     *  MAX timeout (seconds) for route discovery after that timeout
     *  the peer will bail out from finding a route
     */
    private final static long MAXFINDROUTE_TIMEOUT = 60L * TimeUtils.ASECOND;

    /**
     * How long do we wait (seconds) before retrying to make a connection
     * to an endpoint destination
     */
    private final static long MAXASYNC_GETMESSENGER_RETRY = 30L * TimeUtils.ASECOND;

    /**
     * PeerAdv tracking.
     * The peer adv is modified every time a new public address is
     * enabled/disabled. One of such cases is the connection/disconnection
     * from a relay. Since these changes are to the embedded route adv
     * and since we may embbed our route adv in messages, we must keep it
     * up-to-date.
     */
    private PeerAdvertisement lastPeerAdv = null;
    private int lastModCount = -1;

    /**
     * Route info for the local peer (updated along with lastPeerAdv).
     */
    private RouteAdvertisement localRoute = null;

    /**
     * Route Resolver
     * FIXME hamada add resolver support later on
     */
    // private RouteResolver routeResolver = null;

    /**
     *  MessageTransport Control operation
     */
    public final static Integer GET_ROUTE_CONTROL = new Integer(0); // Return RouteControl Object
    public final static int RouteControlOp = 0; // Return RouteControl Object

    protected RouteAdvertisement getMyLocalRoute() {

        // Update our idea of the local peer adv. If it has change,
        // update our idea of the local route adv.
        // If nothing has changed, do not do any work.
        // In either case, return the local route adv as it is after this
        // refresh.

        // Race condition possible but tolerable: if two threads discover
        // the change in the same time, lastPeerAdv and lastModCount
        // could become inconsistent. That'll be straightened out the
        // next time someone looks. The inconsistency can only trigger
        // an extraneous update.

        PeerAdvertisement newPadv = group.getPeerAdvertisement();
        int newModCount = newPadv.getModCount();

        if ((lastPeerAdv != newPadv) || (lastModCount != newModCount) || (null == localRoute)) {
            lastPeerAdv = newPadv;
            lastModCount = newModCount;
        } else {
            // The current version is good.
            return localRoute;
        }

        // Get its EndpointService advertisement
        TextElement endpParam = (TextElement)
                                newPadv.getServiceParam(PeerGroup.endpointClassID);

        if (endpParam == null) {
            if (LOG.isEnabledFor(Level.ERROR)) {
                LOG.error("getMyLocalRoute: no Endpoint SVC Params");
            }

            // Return whatever we had so far.
            return localRoute;
        }

        // get the Route Advertisement element
        Enumeration paramChilds = endpParam.getChildren(RouteAdvertisement.getAdvertisementType());
        Element param = null;

        if (paramChilds.hasMoreElements()) {
            param = (Element) paramChilds.nextElement();
        } else {
            if (LOG.isEnabledFor(Level.ERROR)) {
                LOG.error("getMyLocalRoute: no Endpoint Route Adv");
            }

            // Return whatever we had so far.
            return localRoute;
        }

        // build the new route
        try {
            // Stick the localPeerID in-there, since that was what
            // every single caller of getMyLocalRoute did so far.

            RouteAdvertisement route = (RouteAdvertisement)
                                       AdvertisementFactory.newAdvertisement((TextElement) param);

            route.setDestPeerID((PeerID) localPeerId);
            localRoute = route;
        } catch (Exception ex) {
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.warn("getMyLocalRoute: error extracting route", ex);
            }
        }

        return localRoute;
    }

    /**
     *  listener object to synchronize on asynchronous getMessenger
     */
    private static class EndpointGetMessengerAsyncListener implements MessengerEventListener {

        volatile boolean hasResponse = false;
        volatile boolean isGone = false;
        private Messenger messenger = null;
        private EndpointRouter router = null;
        private EndpointAddress logDest = null;

        /**
         * Constructor
         */
        EndpointGetMessengerAsyncListener(EndpointRouter router, EndpointAddress logDest) {
            this.router = router;
            this.logDest = (EndpointAddress) logDest.clone();
        }

        /**
         *  {@inheritDoc}
         */
        public boolean messengerReady(MessengerEvent event) {

            Messenger toClose = null;

            synchronized (this) {
                hasResponse = true;
                if (event != null) {
                    messenger = event.getMessenger();
                    if (messenger != null && !messenger.getLogicalDestinationAddress().equals(logDest)) {
                        // Ooops, wrong number !
                        toClose = messenger;
                        messenger = null;
                    }
                } else {
                    if (LOG.isEnabledFor(Level.WARN)) {
                        LOG.warn("null messenger event for dest :" + logDest);
                    }
                }
            }

            if (LOG.isEnabledFor(Level.DEBUG)) {
                if (messenger == null) {
                    LOG.debug("error creating messenger for dest :" + logDest);
                } else {
                    LOG.debug("got a new messenger for dest :" + logDest);
                }
            }

            // We had to release the lock on THIS before we can get the lock
            // on the router. (Or face a dead lock - we treat this as a lower
            // level lock)

            if (messenger == null) {

                if (toClose != null) {
                    toClose.close();
                }

                // we failed to get a messenger, we need to update the try and
                // failed as it currently holds an infinite timeout to permit
                // another thread to retry that destination. We only retry
                // every MAXASYNC_GETMESSENGER_RETRY seconds

                router.noMessenger(logDest);

                synchronized (this) {
                    // Only thing that can happen is that we notify for nothing
                    // We took the lock when updating hasResult, so, the event
                    // will not be missed. FIXME. It would be more logical
                    // to let the waiter do the above if (!isGone) as in
                    // the case of success below. However we'll
                    // minimize changes for risk management reasons.
                    notify();
                }
                return false;
            }

            // It worked. Update router cache entry if we have to.

            synchronized (this) {
                if (!isGone) {
                    notify(); // Waiter will do the rest.
                    return true;
                }
            }

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("async caller gone add the messenger " + logDest);
            }
            return router.newMessenger(event);
        }

        /**
         * Wait on the async call for ASYNC_MESSENGER_WAIT
         * then bailout. The messenger will be added whenever
         * the async getMessenger will return
         */
        public synchronized Messenger waitForMessenger(boolean quick) {

            if (!quick) {
                long quitAt = TimeUtils.toAbsoluteTimeMillis(ASYNC_MESSENGER_WAIT);

                while (TimeUtils.toRelativeTimeMillis(quitAt) > 0) {
                    try {
                        // check if we got a response already
                        if (hasResponse) { // ok, we got a response
                            break;
                        }
                        wait(ASYNC_MESSENGER_WAIT);
                    } catch (InterruptedException woken) {
                        Thread.interrupted();
                        break;
                    }
                }
            }

            // mark the fact that the caller is bailing out
            isGone = true;
            return messenger;
        }
    }

    /**
     * how long we are willing to wait for a response from an async
     * getMessenger. We do not wait long at all because it is non-critical
     * that we get the answer synchronously. The goal is to avoid starting
     * a route discovery if there's a chance to get a direct connection.
     * However, we will still take advantage of the direct route if it is
     * found while we wait for the route discovery result. If that happens,
     * the only wrong is that we used some bandwidth doing a route discovery
     * that wasn't needed after all.
     */
    public final static long ASYNC_MESSENGER_WAIT = 3L * TimeUtils.ASECOND;

    /**
     * isLocalRoute is a shalow test. It tells you that there used to be
     * a local route that worked the last time it was tried.
     */
    protected boolean isLocalRoute(EndpointAddress pId) {
        return destinations.isCurrentlyReachable(pId);
    }

    /**
     *  Ensure there is a local route for a given peer id if it can at all be done.
     *
     *  @param pId  the peer who's route is desired.
     *  @param hint specify a specific route hint to use
     *  @return Messenger for local route. null if none could be found or created.
     */

    protected Messenger ensureLocalRoute(EndpointAddress pId, Object hint) {

        // We need to make sure that there is a possible connection to that peer
        // If we have a decent (not closed, busy or available) transport messenger in
        // the pool, then we're done. Else we actively try to make one.

        // See if we already have a messenger.
        Messenger m = destinations.getCurrentMessenger(pId);

        if (m != null) {
            return m;
        }

        // Ok, try and make one. Pass the route hint info
        m = findReachableEndpoint(pId, false, hint);
        if (m == null) {
            // We must also zap it from our positive cache: if we remembered it working, we should think again.
            destinations.noOutgoingMessenger(pId);
            return null; // No way.
        }

        destinations.addOutgoingMessenger(pId, m);

        // We realy did bring something new. Give relief to those that
        // have been waiting for it.
        synchronized (this) {
            notifyAll();
        }

        // NOTE to maintainers: Do not remove any negative cache info
        // or route here. It is being managed by lower-level routines.
        // The presence of a messenger in the pool has many origins,
        // each case is different and taken care of by lower level
        // routines.

        return m;
    }

    /**
     *  Send a message to a given logical destination if it maps to some
     *  messenger in our messenger pool or if such a mapping can be found and
     *  added.
     *
     *  @param destination peer-based address to send the message to.
     *  @param message the message to be sent.
     */
    void sendOnLocalRoute(EndpointAddress destination, Message message)
    throws IOException {

        IOException lastIoe = null;
        Messenger wm;

        // Try as long as we get a transport messenger to try with. They close when they fail, which
        // puts them out of cache or pool if any, so we will not see a broken one a second time. We'll
        // try the next one until we run out of options.

        while ((wm = ensureLocalRoute(destination, null)) != null) {

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("Sending to " + destination + " found a messenger");
            }

            try {

                // FIXME - jice@jxta.org 20040413: May be we should use the non-blocking mode and let excess messages be dropped
                // given the threading issue still existing in the input circuit (while routing messages through).

                wm.sendMessageB(message, EndpointRouter.routerSName, null);

                // If we reached that point, we're done.
                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug("Sending to " + destination + " worked");
                }
                return;

            } catch (IOException ioe) {
                // Can try again, with another messenger (most likely).
                lastIoe = ioe;
            }

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("Trying next messenger to " + destination);
            }
            // try the next messenger if there is one.
        }

        // Now see why we're here.
        // If we're here for no other reason than failing to get a messenger
        // say so. Otherwise, report the failure from the last time
        // we tried.

        if (lastIoe == null) {
            lastIoe = new IOException("No longer any reachable endpoints to destination.");
        }
        if (LOG.isEnabledFor(Level.DEBUG)) {
            LOG.debug("Could not send to " + destination, lastIoe);
        }
        throw (IOException) lastIoe;
    }

    /**
     *  Default constructor
     */
    public EndpointRouter() {

        // routeResolver = new RouteResolver();
    }

    /**
     *  {@inheritDoc}
     */
    public void init(PeerGroup g, ID assignedID, Advertisement impl)
    throws PeerGroupException {

        timer = new Timer(true);
        timer.schedule(new TimerThreadNamer("EndpointRouter Timer for " + g.getPeerGroupID()), 0);
        group = g;
        endpoint = group.getEndpointService();
        localPeerId = group.getPeerID();
        localPeerAddr = new EndpointAddress(routerPName, group.getPeerID().getUniqueValue().toString(), null, null);
        destinations = new Destinations(endpoint);

        // initialize the route resolver
        // FIXME tra 20030818 Should be loaded as service when complete
        // refactoring is done. When loaded as a true service should not
        // have to pass the EnpointRouter object. The issue is we need
        // an api to obtain the real object from the PeerGroup API.
        // FIXME add resolver support later on
        // routeResolver.init(g, assignedID, impl, this);

        endpoint.addIncomingMessageListener(this, routerSName, null);
        if (endpoint.addMessageTransport(this) == null) {
            throw new PeerGroupException("Transport registration refused");
        }

        if (LOG.isEnabledFor(Level.INFO)) {
            StringBuffer configInfo = new StringBuffer("Configuring Router Transport : " + assignedID);

            configInfo.append("\n\tGroup Params:");
            configInfo.append("\n\t\tGroup: " + group.getPeerGroupName());
            configInfo.append("\n\t\tGroup ID: " + group.getPeerGroupID());
            configInfo.append("\n\t\tPeer ID: " + group.getPeerID());

            configInfo.append("\n\tConfiguration :");
            configInfo.append("\n\t\tPeerID : " + localPeerId);
            configInfo.append("\n\t\tPublic Address : " + localPeerAddr);
            //configInfo.append("\n\t\tUse RouteResolver : " + routeResolver.useRouteResolver());

            LOG.info(configInfo);
        }
    }

    /**
     *  {@inheritDoc}
     */
    public int startApp(String[] arg) {

        int status = 0;
        // FIXME tra 20031015 is there a risk for double
        // registration  when startApp() is recalled
        // due to failure to get the discovery service
        // in the previous statement.

        // NOTE: Endpoint needs to be registered before
        // we register the endpoint resolver. This is
        // bringing a more complex issue of service
        // loading dependencies.
        endpoint.addMessengerEventListener(this, EndpointService.MediumPrecedence);

        // FIXME hamada add routeResolver later on
        // status = routeResolver.startApp(arg);
        if (status != 0) {
            return status;
        }

        if (LOG.isEnabledFor(Level.INFO)) {
            LOG.info("Router Message Transport started");
        }

        return status;
    }

    /**
     *  {@inheritDoc}
     *
     * <p/>Careful that stopApp() could in theory be called before startApp().
     */
    public void stopApp() {

        if (endpoint != null) {
            endpoint.removeIncomingMessageListener(routerSName, null);
            endpoint.removeMessengerEventListener(this, EndpointService.MediumPrecedence);
            endpoint.removeMessageTransport(this);
            endpoint = null;
        }

        // FIXME tra 20030818 should be unloaded as a service
        //routeResolver.stopApp();

        destinations.close();
        timer.cancel();
        timer = null;
        //routeResolver = null;

        if (LOG.isEnabledFor(Level.INFO)) {
            LOG.info("Router Message Transport stopped");
        }
    }

    /**
     *  {@inheritDoc}
     */
    public boolean isConnectionOriented() {
        return false;
    }

    /**
     *  {@inheritDoc}
     */
    public boolean allowsRouting() {
        // Yes, this is the router, and it does not allow routing.
        // Otherwise we would have a chicken and egg problem.
        return false;
    }

    /**
     *  {@inheritDoc}
     */
    public EndpointService getEndpointService() {
        return endpoint;
    }

    /**
     *  {@inheritDoc}
     */
    public EndpointAddress getPublicAddress() {
        return (EndpointAddress) localPeerAddr.clone();
    }

    /**
     *  {@inheritDoc}
     */
    public Iterator getPublicAddresses() {
        return Collections.singletonList(getPublicAddress()).iterator();
    }

    /**
     *  {@inheritDoc}
     */
    public String getProtocolName() {
        return routerPName;
    }

    /**
     *(@inheritdoc}
     */
    public boolean isPropagateEnabled() {

        return false;
    }

    /**
     *(@inheritdoc}
     */
    public boolean isPropagationSupported() {

        return false;
    }

    /**
     *  {@inheritDoc}
     */
    public void propagate(Message srcMsg, String pName, String pParam, String prunePeer) throws IOException {// All messages are lost in the ether
    }

    /**
     * Given a peer id, return an address to reach that peer.
     * The address may be for a directly reachable peer, or
     * for the first gateway along a route to reach the peer.
     * If we do not have a route to the peer, we will use the
     * Peer Routing Protocol to try to discover one.  We will
     * wait up to 30 seconds for a route to be discovered.
     *
     * @param dest the peer we are trying to reach.
     * @param seekRoute whether to go as far as issuing a route query, or just fish in our cache.
     * when forwarding a message we allow ourselves to mend a broken source-issued route but we
     * won't go as far as seeking one from other peers. When originating a message, on the other end
     * we will aggressively try to find route.
     * @param hint whether we are passed a route hint to be used, in that case that route
     * hint should be used
     *
     * @return an EndpointAddress at which that peer should be reachable.
     */

    EndpointAddress getGatewayAddress(EndpointAddress dest, boolean seekRoute) {
        return getGatewayAddress(dest, seekRoute, null);
    }

    EndpointAddress getGatewayAddress(EndpointAddress dest, boolean seekRoute, Object hint) {

        try {
            EndpointAddress pId = new EndpointAddress(dest, null, null);

            // FIXME: jice@jxta.org - 20021215 replace that junk with a background
            // task; separate the timings of route disco from the timeouts of
            // the requesting threads. EndpointAddress result = null;

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("Searching local" + (seekRoute ? " & remote" : "") + " for route for " + pId);
            }

            // If we can't get a route within the timeout, give up for now.
            long quitAt = TimeUtils.toAbsoluteTimeMillis(MAXFINDROUTE_TIMEOUT);

            // Time we need to wait before we can start issue a find route request
            // to give a chance for the async messenger to respond (success or failure)
            long findRouteAt = TimeUtils.toAbsoluteTimeMillis(ASYNC_MESSENGER_WAIT);

            EndpointAddress addr = null;

            while (TimeUtils.toRelativeTimeMillis(quitAt) > 0) {
                // Then check if by any chance we can talk to it directly.
                if (ensureLocalRoute(pId, hint) != null) {
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("Found direct address " + pId);
                    }

                    return pId;
                }

                // Otherwise, look for a long route.
                // check if we got a hint. If that's the case use it
                RouteAdvertisement route = null;

                if (hint != null) {
                    route = (RouteAdvertisement) hint;
                } else {
                    route = getRoute(pId, seekRoute);
                }

                if (route != null && route.size() > 0) {

                    addr = pid2addr(route.getLastHop().getPeerID());
                    if (ensureLocalRoute(addr, null) != null) {
                        if (LOG.isEnabledFor(Level.DEBUG)) {
                            LOG.debug("Found last hop remote address: " + pId + " -> " + route.getLastHop().getPeerID());
                        }

                        // Ensure local route removes negative cache info about
                        // addr. We also need to remove that about pId.
                        return addr;

                    } else { // need to try the first hop
                        addr = pid2addr(route.getFirstHop().getPeerID());

                        if (ensureLocalRoute(addr, null) != null) {
                            if (LOG.isEnabledFor(Level.DEBUG)) {
                                LOG.debug("Found first hop remote address first hop: " + pId + " -> " + route.getFirstHop().getPeerID());
                            }

                            // Ensure local route removes negative cache info about
                            // addr.
                            return addr;

                        } else {

                            removeRoute(pId);
                            if (LOG.isEnabledFor(Level.DEBUG)) {
                                LOG.debug("Found no reachable route to " + pId);
                            }
                        }
                    }
                }

                // For messages we didn't originate we don't seek routes.
                if (!seekRoute) {
                    break;
                }

                // Check that route resolution is enabled if
                // not then bail out, there is nothing more
                // that we can do.
                // FIXME hamada add resolver support later on
                // if (!routeResolver.useRouteResolver()) {
                //    break;
                //}

                // due to the asynchronous nature of getting our messenger we
                // need to handle the multi-entrance of issueing a route
                // discovery. A route discovery needs to be generated only
                // either if we have no pending request (it completed or we had
                // no information so we did not created one), or we tried and
                // we failed, or we waited at least ASYNC_MESSENGER_WAIT to get
                // a chance for the async request to respond before we can
                // issue the route discovery
                Long nextTry = (Long) triedAndFailed.get(pId);

                if ((nextTry == null) || (nextTry.longValue() < TimeUtils.toAbsoluteTimeMillis(MAXASYNC_GETMESSENGER_RETRY))
                    || (TimeUtils.toRelativeTimeMillis(findRouteAt) <= 0)) {

                    // If it is already hopeless (negative cache), just give up.
                    // Otherwise, try and recover the route. If a query is not
                    // already pending, we may trigger a route discovery before we
                    // wait. Else, just wait. The main problem we have here is that
                    // the same may re-enter because the resolver query sent by
                    // findRoute ends up with the rendezvous service trying to
                    // resolve the same destiation if the destination  happens to be
                    // the start of the walk. In that situation we will re-enter
                    // at every findRoute attempt until the query becomes "failed".
                    // However, we do want to do more than one findRoute because
                    // just one attempt can fail for totaly fortuitous or temporary
                    // reasons. A tradeoff is to do a very limitted number of attempts
                    // but still more than one. Over the minute for which the query
                    // is not failed, isTimeToRety will return true at most twice
                    // so that'll be a total of three attempts: once every 20 seconds.
                    boolean doFind = false;
                    ClearPendingQuery t = null;

                    synchronized (this) {
                        t = (ClearPendingQuery) pendingQueries.get(pId);

                        if (t == null) {
                            doFind = true;
                            t = new ClearPendingQuery(pId);
                            pendingQueries.put(pId, t);
                        } else {
                            if (t.isFailed()) {
                                break;
                            }
                            if (t.isTimeToRetry()) {
                                doFind = true;
                            }
                        }
                    }

                    // protect against the async messenger request. We only
                    // look for a route after the first iteration by
                    // that time we will have bailed out from the async call
                    /*
                    if (doFind) {
                        routeResolver.findRoute(pId);
                        // we do not need to check the CM, route table will
                        // be updated when the route response arrive. This reduces
                        // CM activities when we wait for the route response
                        seekRoute = false;
                    }
                    */
                }

                // Now, wait. Responses to our query may occur asynchronously.
                // threads.
                synchronized (this) {
                    // We can't possibly do everything above while synchronized,
                    // so we could miss an event of interrest. But some changes
                    // are not readily noticeable anyway, so we must wake up
                    // every so often to retry.
                    try {
                        // we only need to wait if we haven't got a messenger
                        // yet.
                        if (destinations.getCurrentMessenger(pId) == null) {
                            wait(ASYNC_MESSENGER_WAIT);
                        }
                    } catch (InterruptedException woken) {
                        Thread.interrupted();
                    }
                }
            }

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("No route to " + pId);
            }

            return null;

        } catch (Exception ex) {
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.warn("getGatewayAddress exception", ex);
            }

            return null;
        }
    }

    /**
     * Returns true if the target address is reachable. Otherwise
     * returns false.
     */
    public boolean ping(EndpointAddress addr) {

        EndpointAddress plainAddr = new EndpointAddress(addr, null, null);

        try {
            return (getGatewayAddress(plainAddr, true) != null);
        } catch (Exception e) {
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.debug("Ping failure (exception) for : " + plainAddr, e);
            }
            return false;
        }
    }

    /**
     * Receives notifications of new messengers being generated by the
     * underlying network transports.
     *
     * <p/>IMPORTANT: Incoming messengers only. If/when this is used for
     * outgoing, some things have to change:
     *
     * <p/>For example we do not need to send the welcome msg, but
     * for outgoing messengers, we would need to.
     *
     * <p/>Currently, newMessenger handles the outgoing side.
     *
     *    @param event    the new messenger event.
     */
    public boolean messengerReady(MessengerEvent event) {

        Messenger messenger = event.getMessenger();

        Object source = event.getSource();

        EndpointAddress logDest = messenger.getLogicalDestinationAddress();

        if (source instanceof MessageSender && !((MessageSender) source).allowsRouting()) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("Ignoring messenger to :" + logDest);
            }
            return false;
        }

        // We learned that a transport messenger has just been
        // announced.  Noone else took it, so far, so we'll take
        // it. Incoming messengers are not pooled by the endpoint
        // service. We do pool them for our exclusive use.

        boolean taken = destinations.addIncomingMessenger(logDest, messenger);

        // Note to maintainers: Do not remove any route or negative
        // cache info here. Here is why: The messenger we just
        // obtained was made out of an incoming connection. It brings
        // no proof whatsoever that the peer is reachable at our
        // initiative. In general, there is nothing to gain in
        // removing our knowlege of a long route, or a pending route
        // query, or a triedAndFailed record, other than to force
        // trying a newly obtained set of addresses. They will not
        // stop us from using this messenger as long as it works.
        // The only good thing we can do here, is waking up those
        // that may be waiting for a connection.

        synchronized (this) {
            notifyAll();
        }

        return taken;
    }

    /**
     *  call when an asynchronous new messenger could not be obtained.
     *
     *    @param logDest the failed logical destination
     */
    public void noMessenger(EndpointAddress logDest) {

        // Switch to short timeout if there was an infinite one.
        // Note if there's one, it is either short or inifinite. So we
        // look at the value only in the hope it is less expensive
        // than doing a redundant put.

        synchronized (this) {
            Long curr = (Long) triedAndFailed.get(logDest);

            if (curr != null && curr.longValue() > TimeUtils.toAbsoluteTimeMillis(MAXASYNC_GETMESSENGER_RETRY)) {
                triedAndFailed.put(logDest, new Long(TimeUtils.toAbsoluteTimeMillis(MAXASYNC_GETMESSENGER_RETRY)));
            }
        }
    }

    /**
     * call when an asynchronous new messenger is ready.
     * (name is not great).
     *
     * @param event the new messenger event.
     */
    public boolean newMessenger(MessengerEvent event) {

        Messenger messenger = event.getMessenger();
        EndpointAddress logDest = messenger.getLogicalDestinationAddress();

        // We learned that a new transport messenger has just been announced.
        // We pool it for our exclusive use.
        destinations.addOutgoingMessenger(logDest, messenger);

        // Here's a new connection. Wakeup those that may be waiting
        // for that.
        synchronized (this) {
            notifyAll();
        }

        return true;
    }

    /**
     *  Get the routed route, if any, for a given peer id.
     *
     *  @param pId  the peer who's route is desired.
     *  @param seekRoute boolean to indicate  if we should search for a route
     *         if we don't have one
     *  @return a route advertisement describing the direct route to the peer.
     */
    protected RouteAdvertisement getRoute(EndpointAddress pId, boolean seekRoute) {

        // check if we have a valid route
        RouteAdvertisement route = null;

        synchronized (this) {
            route = (RouteAdvertisement) routedRoutes.get(pId);
        }

        if (route != null || !seekRoute) { // done
            return route;
        }
        return null;
    }

    // Check if a route is valid.
    // Currently, only loops are detected.
    private boolean checkRoute(RouteAdvertisement r) {

        if (r == null) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("route is null");
            }
            return false;
        }

        if (r.size() == 0) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("route is empty");
            }
            return false;
        }

        if (r.containsHop((PeerID) localPeerId)) {
            // The route does contain this local peer. Using this route
            // would create a loop. Discard.
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("route contains this peer - loopback");
            }
            return false;
        }

        PeerID destPid = r.getDest().getPeerID();

        if (r.containsHop(destPid)) {
            // May be it is fixable, may be not. See to it.
            Vector hops = r.getVectorHops();

            // It better be the last hop. Else this is a broken route.
            hops.remove(hops.lastElement());

            if (r.containsHop(destPid)) {
                // There was one other than last: broken route.
                return false;
            }
        }

        if (r.hasALoop()) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("route has a loop ");
            }
            return false;
        } else {
            // Seems to be a potential good route.
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("route is ok");
            }
            return true;
        }
    }

    // Adds a new long route provided there not a direct one already.
    // Replaces any longer route.  return true if the route was truely new.
    // The whole deal must be synch. We do not want to add a long route
    // while a direct one is being added in parallell or other stupid things like that.

    /**
     * set new route info
     *
     * @param r new route to learn
     * @param force true if the route was optained by receiving
     *                a message
     */
    protected boolean setRoute(RouteAdvertisement r,
                               boolean force) {
        PeerID pid = null;
        EndpointAddress pidAddr = null;
        boolean pushNeeded = false;
        boolean status = false;

        if (LOG.isEnabledFor(Level.DEBUG)) {
            LOG.debug("setRoute:");
        }

        if (r == null) {
            return false;
        }

        synchronized (this) {
            try {
                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug(r.display());
                }

                pid = r.getDest().getPeerID();
                pidAddr = pid2addr(pid);

                // Check if we are in the case where we are
                // setting a new route as we received a message
                // always force the new route setup when we received a
                // a message

                if (!force) {
                    // check if we have some bad NACK route info for
                    // this destination
                    BadRoute badRoute = (BadRoute) badRoutes.get(pidAddr);

                    if (badRoute != null) {
                        Long nextTry = badRoute.getExpiration();

                        if (nextTry.longValue() > System.currentTimeMillis()) {

                            // check if the route we have in the NACK cache match the
                            // new one. Need to make sure that we clean the route
                            // from any endpoint addresses as the badRoute cache only
                            // contains PeerIDs
                            RouteAdvertisement routeClean = (RouteAdvertisement) r.cloneOnlyPIDs();

                            if (routeClean.equals(badRoute.getRoute())) {
                                if (LOG.isEnabledFor(Level.DEBUG)) {
                                    LOG.debug("try to use a known bad route");
                                }
                                return false;
                            }
                        } else { // expired info, just flush NACK route cache
                            badRoutes.remove(pidAddr);
                        }
                    }
                } else {
                    // we get a new route
                    badRoutes.remove(pidAddr);
                }

                // Check if the route makes senses (loop detection)
                if (!checkRoute(r)) {
                    // Route is invalid. Drop it.
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("Route is invalid");
                    }
                    return false;
                }

                // check if we can reach the first hop in the route
                // We only do a shallow test of the first hop. Whether more effort
                // is worth doing or not is decided (and done) by the invoker.
                if (!isLocalRoute(pid2addr(r.getFirstHop().getPeerID()))) {
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("Unreachable route - ignore");
                    }
                    return false;
                }

            } catch (Exception ez1) {
                // The vector must be empty, which is not supposed
                // to happen.
                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug("Got an empty route - discard" + r.display());
                }
                return false;
            }

            // add the new route
            try {
                // push the route to SRDI only if it is a new route. the intent is
                // to minimize SRDI traffic. The SRDIinformation is more of the order
                // this peer has a route to this destination, it does not need to be
                // updated verey time the route is updated. Information about knowing
                // that this peer has a route is more important that the precise
                // route information

                // SRDI is run only if the peer is acting as a rendezvous
                if (group.isRendezvous()) {
                    if (!routedRoutes.containsKey(pidAddr)) {
                        if (LOG.isEnabledFor(Level.DEBUG)) {
                            LOG.debug("push new SRDI route " + pid);
                        }
                        pushNeeded = true;
                    }
                }

                // Remove any endpoint addresses from the route
                // as part of the cloning. We just keep track
                // of PIDs in our route table
                RouteAdvertisement newRoute = (RouteAdvertisement) r.cloneOnlyPIDs();

                routedRoutes.put(pidAddr, newRoute);

                // We can get rid of any negative info we had. We have
                // a new and different route.
                badRoutes.remove(pidAddr);

                notifyAll(); // Wakeup those waiting for a route.

                status = true;
            } catch (Exception e2) {
                // We failed, leave things as they are.
                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug("   failed setting route with " + e2);
                }
                status = false;
            }
        }
        return status;
    }

    /**
     * This method is used to remove a route
     *
     * @param pId  route to peerid to be removed
     */
    protected void removeRoute(EndpointAddress pId) {

        boolean needRemove;

        synchronized (this) {
            needRemove = false;
            if (routedRoutes.containsKey(pId)) {
                if (group.isRendezvous()) {
                    // Remove the SRDI cache entry from the SRDI cache
                    needRemove = true;
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("remove SRDI route " + pId);
                    }
                }
                routedRoutes.remove(pId);
            }
        }
    }

    /**
     *  {@inheritDoc}
     */
    public void processIncomingMessage(Message msg,
                                       EndpointAddress srcAddr,
                                       EndpointAddress dstAddr) {

        EndpointAddress srcPeer = null; // The originating peer
        EndpointAddress destPeer = null; // The destination peer
        EndpointAddress lastHop = null; // The last peer that routed this to us
        boolean connectLastHop = false;

        ; // true:Try connecting to lastHop

        EndpointAddress origSrcAddr = null; // The origin endpointAddr (jxta:)
        EndpointAddress origDstAddr = null; // The dest endpointAddr   (jxta:)
        Vector origHops = null; // original route of the message

        EndpointRouterMessage routerMsg = null;

        EndpointAddress nextHop = null;
        RouteAdvertisement radv = null;

        // We do not want the existing header to be ignored of course.
        routerMsg = new EndpointRouterMessage(msg, false);

        if (!routerMsg.msgExists()) {

            // The sender did not use this router
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("processIncomingMessage: no routing info");
            }
            return;
        }

        try {

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug(routerMsg.display());
            }

            origSrcAddr = routerMsg.getSrcAddress();

            origDstAddr = routerMsg.getDestAddress();

            // convert the src and dest addresses into canonical
            // form stripping service info
            srcPeer = new EndpointAddress(origSrcAddr, null, null);
            destPeer = new EndpointAddress(origDstAddr, null, null);

            if (routerMsg.getLastHop() != null) {
                lastHop = new EndpointAddress(routerMsg.getLastHop());
            }

            // See if there's an originator full route adv inthere.
            // That's a good thing to keep.
            radv = routerMsg.getRouteAdv();
            if (radv != null) {

                // publish the full route adv. Also, leave it the
                // message.  It turns out to be extremely usefull to
                // peers downstream, specially the destination. If
                // this here peer wants to embed his own radv, it will
                // have to wait; the one in the message may not come
                // again.

                // FIXME - jice@jxta.org 20040413 : all this could wait (couldn't it ?)
                // until we know it's needed, therefore parsing could wait as well.

                // Looks like a safe bet to try and ensure a
                // connection in the opposite direction if there
                // isn't one already.

                if (pid2addr(radv.getDestPeerID()).equals(lastHop)) {
                    connectLastHop = true;
                }

                // Make the most of that new adv.
                setRoute(radv, true);
                updateRouteAdv(radv);
            }

        } catch (Exception badHdr) {
            // Drop it, we do not even know the destination
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.warn("Bad routing header or bad message. Message dropped.");
            }
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("Exception: ", badHdr);
            }
            return;
        }

        // Is this a loopback ?
        if ((srcPeer != null) && srcPeer.equals(localPeerAddr)) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("processIncomingMessage: dropped loopback");
            }
            return;
        }

        // Are we already sending to ourself. This may occur
        // if some old advertisements for our EA is still
        // floating around
        if ((lastHop != null) && lastHop.equals(localPeerAddr)) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("processIncomingMessage: dropped loopback from impersonating Peer");
            }
            return;
        }

        // We have to try and reciprocate the connection, so that we
        // have chance to learn reverse routes early enough.  If we do
        // not already have a messenger, then we must know a route adv
        // for that peer in order to be able to connect. Otherwise,
        // the attempt will fail and we'll be left with a negative
        // entry without having realy tried anything.  To prevent that
        // we rely on the presence of a radv in the router message. If
        // there's no radv, two possibilities:
        //
        // - It is not the first contact from that peer and we already
        // have tried (with or without success) to reciprocate.
        //
        // - It is the first contact from that peer but it has not
        // embedded its radv. In the most likely case (an edge peer
        // connecting to a rdv), the edge peer will have no difficulty
        // finding the reverse route, provided that we do not make a
        // failed attempt right now.
        //
        // Conclusion: if there's no embedded radv in the message, do
        // nothing.

        if (connectLastHop) {
            ensureLocalRoute(lastHop, radv);
        }

        try {

            // Normalize the reverseHops vector from the message and, if it
            // looks well formed and usefull, learn from it.  Do we have a
            // direct route to the origin ?  If yes, we'll zap the revers
            // route in the message: we're a much better router.  else,
            // learn from it. As a principle we regard given routes to be
            // better than existing ones.
            Vector reverseHops = routerMsg.getReverseHops();

            if (reverseHops == null) {
                reverseHops = new Vector();
            }

            // check if we do not have a direct route
            // in that case we don't care to learn thelong route
            if (!isLocalRoute(srcPeer)) {
                // Check if the reverseRoute info looks correct.
                if (lastHop != null) {
                    // since we are putting the lasthop in the
                    // reverse route to indicate the validity of
                    // lastop to reach the previous hop, we have
                    // the complete route, just clone it. (newRoute
                    // owns the given vector)

                    if ((reverseHops.size() > 0) && ((AccessPointAdvertisement) reverseHops.firstElement()).getPeerID().equals(addr2pid(lastHop))) {

                        // Looks like a good route to learn
                        setRoute(RouteAdvertisement.newRoute(addr2pid(srcPeer), (Vector) reverseHops.clone()), true);

                    }

                }
            }

            // If this peer is the final destination, then we're done. Just let
            // it be pushed up the stack. We must not leave our header;
            // it is now meaningless and none of the upper layers business.
            // All we have to do is to supply the end-2-end src and dest
            // so that the endpoint demux routine can do its job.
            if (destPeer.equals(localPeerAddr)) {

                // Removing the header.
                routerMsg.clearAll();
                routerMsg.updateMessage();

                // receive locally
                endpoint.processIncomingMessage(msg, origSrcAddr, origDstAddr);

                return;
            }

            // WATCHOUT: if this peer is part of the reverse route
            // it means that we've seen that message already: there's
            // a loop between routers ! If that happens drop that
            // message as if it was burning our fingers

            // First build the ap that we might add to the reverse route.
            // We need it to look for ourselves in reverseHops. (contains
            // uses equals(). equals will work because we always include
            // in reversehops aps that have only a pid.

            AccessPointAdvertisement selfAp = (AccessPointAdvertisement)
                                              AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());

            selfAp.setPeerID(addr2pid(localPeerAddr));

            if (reverseHops.contains(selfAp)) {

                // Danger, bail out !
                // Better not to try to NACK for now, but get rid of our own
                // route. If we're sollicited again, there won't be a loop
                // and we may NACK.

                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug("Routing loop detected. Message dropped");
                }
                removeRoute(destPeer);
                return;
            }

            // Update reverseHops. That is, add ourselves to the list.

            // We will only add the current hop to the reverse
            // route if we know how to talk back to the previous hop
            // it is important to point the difference between the lastHop
            // and the reverse route entry. The lastHop indicates where the
            // message came from but does not specify whether it is able to
            // route messages in the other direction. The reverse route, if
            // present, provides that information.

            // FIXME - jice@jxta.org 20040413 : HERE comes the use of connectLastHop. Could have waited till here.

            if (isLocalRoute(lastHop)) { // ok we have direct route back, at least we hope :-)

                reverseHops.add(0, selfAp); // Update our vector
                routerMsg.prependReverseHop(selfAp); // Update the message, this preserves the cache.

            }
            else {

                // We cannot talk to our previous hop, well
                // check if we have route to the src and use it as
                // our reverse route. We could do more. But let's keep
                // it to the minimum at this point.
                RouteAdvertisement newReverseRoute = (RouteAdvertisement) routedRoutes.get(srcPeer);

                if (newReverseRoute != null) {
                    // we found a new route back from our cache so let's use it
                    reverseHops = (Vector)
                                  newReverseRoute.getVectorHops().clone();

                    // ok add ourselve to the reverse route
                    reverseHops.add(0, selfAp);

                } else {
                    // no new route found, sorry. In the worst
                    // case it is better to not have reverse route
                    reverseHops = null;
                }

                // In both cases above, we replace the hops completely.
                // The cache is of no use and is lost.
                routerMsg.setReverseHops(reverseHops);
            }

            // Get the next peer into the forward route
            origHops = routerMsg.getForwardHops();
            if (origHops != null) {
                nextHop = getNextHop(origHops);
            }

            // see if we can shortcut to the destination with no effort.
            // If that works it's all very easy.
            if (isLocalRoute(destPeer)) {

                // There is a local route - use it
                // Clear the forward path which is probably wrong
                routerMsg.setForwardHops(null);
                nextHop = (EndpointAddress) destPeer.clone();

            } else {

                if (nextHop == null) {

                    // No next hop. Use the destPeer instead. (but, unlike when
                    // we shortcut it deliberately, don't know if we can have a direct route
                    // yet). This is perfectly normal if we're just the last
                    // hop before the destination and we have closed the direct connection
                    // with it since we declared to be a router to it.

                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("No next hop in forward route - Using destination as next hop");
                    }
                    nextHop = (EndpointAddress) destPeer.clone();

                    // That forward path is exhausted. It will not be usefull anymore.
                    // either we reach the destination directly and there will be
                    // no need for a NACK further down, or we will need to find an alternate
                    // route.
                    routerMsg.setForwardHops(null);
                }

                // We must be do better than look passively for a direct
                // route. The negative cache will take care of reducing the
                // implied load. If we do not, then we never re-establish
                // a broken local route until the originating peer seeks a
                // new route. Then the result is roughly the same plus
                // the overhead of route seeking...worse, if we're in the
                // path from the originator to it's only rdv, then the
                // originator does not stand a chance until it re-seeds !

                if (ensureLocalRoute(nextHop, null) == null) { // Here we go :-)

                    // need to look for a long route.
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("Forward route element broken - trying alternate route");
                    }

                    // While we're at it, we might as well get rid of our own
                    // route to the destination if it goes through the same hop
                    // by any chance.
                    // FIXME: idealy, each time we get a broken local route
                    // we'd want to get rid of all routes that start from there
                    // but that's one more map to maintain.

                    RouteAdvertisement route = getRoute(destPeer, false);

                    if (route == null) {
                        cantRoute("No new route to repair the route - drop message", null, origSrcAddr, destPeer, origHops);
                        return;
                    }

                    if (pid2addr(route.getFirstHop().getPeerID()).equals(nextHop)) {
                        // Our own route is just as rotten as the sender's. Get rid
                        // of it.
                        removeRoute(destPeer);
                        cantRoute("No better route to repair the route - drop message", null, origSrcAddr, destPeer, origHops);
                        return;
                    }

                    // optimization to see if we can reach
                    // directly the last hop of that route
                    EndpointAddress addr = pid2addr(route.getLastHop().getPeerID());

                    if (isLocalRoute(addr)) {

                        // FIXME - jice@jxta.org 20030723. Should update our route
                        // table to reflect the shortcut.

                        if (LOG.isEnabledFor(Level.DEBUG)) {
                            LOG.debug("Found new remote route via : " + addr);
                        }

                        // set the forward path to null no next hop
                        // FIXME: Not true. the last hop is not the destination.
                        // There could be a need for us receiving a NACK and that won't be
                        // possible. We should leave the next hop in the fw path. Just like
                        // we do when forwarding along the existing route.

                        routerMsg.setForwardHops(null);

                    } else { // need to check the first hop

                        Vector newHops = (Vector) route.getVectorHops().clone();

                        // FIXME: remove(0) seems wrong
                        // There could be a need for us receiving a NACK and that won't be
                        // possible. We should leave the next hop in the fw path. Just like
                        // we do when forwarding along the existing route.

                        addr = pid2addr(((AccessPointAdvertisement) newHops.remove(0)).getPeerID());

                        if (!isLocalRoute(addr)) {
                            // Our own route is provably rotten
                            // as well. Get rid of it.
                            removeRoute(destPeer);
                            cantRoute("No usable route to repair the route - drop message", null, origSrcAddr, destPeer, origHops);

                            return;
                        }
                        if (LOG.isEnabledFor(Level.DEBUG)) {
                            LOG.debug("Found new remote route via : " + addr);
                        }

                        // NB: setForwardHops does not clone.
                        routerMsg.setForwardHops(newHops);
                    }

                    // If we're here. addr is our new nextHop.
                    nextHop = addr;
                }
            }

            // The first time we talk to a peer to which we have
            // initiated a connection, we must include our local
            // route adv in the routerMsg. However, we give priority to
            // a route adv that's already in the message and which we pass along.
            // In that case, our own will go next time. Note: we care only for
            // nextHop, not for the final destination. We give our radv to a far
            // destination only if we originate a message to it; not when forwarding.
            // JC: give priority to our own radv instead. It can be critical.

            // 20040301 tra: May be the case we haven't yet initialize our
            // own local route. For example still waiting for our relay connection
            RouteAdvertisement myRoute = getMyLocalRoute();

            if ((myRoute != null) && destinations.isWelcomeNeeded(nextHop)) {
                routerMsg.setRouteAdv(myRoute);
            }

            // We always modify the router message within the message
            routerMsg.setLastHop(localPeerAddr.toString());
            routerMsg.updateMessage();

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("Trying to forward to " + nextHop);
            }

            sendOnLocalRoute(nextHop, msg);

            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("Successfully forwarded to " + nextHop);
            }

        } catch (Exception e) {
            cantRoute("failed to deliver or forward message: ", e, origSrcAddr, destPeer, origHops);
        }
    }

    private void cantRoute(String logMsg, Exception e, EndpointAddress origSrcAddr,
                           EndpointAddress destPeer, Vector origHops) {

        if (LOG.isEnabledFor(Level.DEBUG)) {
            if (e == null) {
                LOG.debug(logMsg);
            } else {
                LOG.debug(logMsg, e);
            }
        }
        //FIXME add resolver support later on
        //routeResolver.generateNACKRoute(addr2pid(origSrcAddr), addr2pid(destPeer), origHops);

    }

    /**
     * Return the address of the next hop in this vector
     *
     * @param list of forward hops in the route
     * @return next hop to be used
     */
    private EndpointAddress getNextHop(Vector hops) {

        // check if we have a real route
        if ((hops == null) || (hops.size() == 0)) {
            return null;
        }

        // find the next hop.
        for (Enumeration e = hops.elements(); e.hasMoreElements();) {
            AccessPointAdvertisement ap = (AccessPointAdvertisement) e.nextElement();

            if (localPeerId.equals(ap.getPeerID())) {

                // If found at the end, no next hop
                if (!e.hasMoreElements()) {
                    return null;
                }

                return pid2addr(((AccessPointAdvertisement) e.nextElement()).getPeerID());
            }
        }

        // The peer is not into the vector. Since we have got that
        // message, the best we can do is to send it to the first gateway
        // in the forward path.
        return pid2addr(((AccessPointAdvertisement) hops.elementAt(0)).getPeerID());
    }

    /**
     *  lame hardcoding
     */
    private boolean isFast(MessageTransport p) {
        String name = p.getProtocolName();

        return name.equals("tcp") || name.equals("beep");
    }

    private boolean isRelay(MessageTransport p) {
        String name = p.getProtocolName();

        return name.equals("relay");
    }

    /**
     * Given a list of addresses, find the best reachable endpoint.
     *
     * <ul>
     *      <li>The address returned must be reachable.</li>
     *      <li>We prefer an address whose protocol is, in order:</li>
     *          <ol>
     *              <li>connected and fast.</li>
     *              <li>connected and slow.</li>
     *              <li>unconnected and fast.</li>
     *              <li>unconnected and slow</li>
     *          </ol></li>
     *  </ul>
     *
     * @param addrs A list of addresses to evaulate reachability.
     * @param exist true if there already are existing messengers for
     * the given destinations but we want one more. It may lead us to reject
     * certain addresses that we would otherwise accept.
     * @return The endpoint address for which we found a local route otherwise
     * null
     */
    Messenger findBestReachableEndpoint(EndpointAddress dest, List mightWork, boolean exist) {

        List rankings = new ArrayList(mightWork.size());
        List worthTrying = new ArrayList(mightWork.size());

        // First rank the available addresses by type rejecting those which
        // cant be used.
        Iterator eachMightWork = mightWork.iterator();

        while (eachMightWork.hasNext()) {
            EndpointAddress addr = (EndpointAddress) eachMightWork.next();

            // skip our own type
            if (getProtocolName().equals(addr.getProtocolName())) {
                continue;
            }

            int rank = -1;

            Iterator eachTransport = endpoint.getAllMessageTransports();

            while (eachTransport.hasNext()) {
                MessageTransport transpt = (MessageTransport) eachTransport.next();

                if (!transpt.getProtocolName().equals(addr.getProtocolName())) {
                    continue;
                }

                // must be a sender
                if (!(transpt instanceof MessageSender)) {
                    continue;
                }

                MessageSender sender = (MessageSender) transpt;

                // must allow routing
                if (!sender.allowsRouting()) {
                    // This protocol should not be used for routing.
                    continue;
                }

                rank += 1;

                if (sender.isConnectionOriented()) {
                    rank += 2;
                }

                if (isRelay(transpt)) {
                    // That should prevent the relay for ever being used
                    // when the relay may be sending through the router.
                    if (exist) {
                        rank -= 1000;
                    }
                }

                if (isFast(transpt)) {
                    rank += 4;
                }
            }

            // if its worth trying then insert it into the rankings.
            if (rank >= 0) {
                for (int eachCurrent = 0; eachCurrent <= rankings.size(); eachCurrent++) {
                    if (rankings.size() == eachCurrent) {
                        rankings.add(new Integer(rank));
                        worthTrying.add(addr);
                        break;
                    }

                    if (rank > ((Integer) rankings.get(eachCurrent)).intValue()) {
                        rankings.add(eachCurrent, new Integer(rank));
                        worthTrying.add(eachCurrent, addr);
                        break;
                    }
                }
            }
        }

        // now that we have them ranked, go through them until we get a
        // successful messenger.
        rankings = null;
        Iterator eachWorthTrying = worthTrying.iterator();

        while (eachWorthTrying.hasNext()) {
            EndpointAddress addr = null;

            try {
                addr = (EndpointAddress) eachWorthTrying.next();
                eachWorthTrying.remove();

                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug("getBestLocalRoute - Trying : " + addr);
                }

                // We use an async getMessenger as we do not
                // want to wait too long to obtain our messenger
                // We will still wait ASYNCMESSENGER_WAIT to see
                // if we can get the messenger before bailing out
                Messenger messenger = null;

                // Create the listener object for that request
                EndpointGetMessengerAsyncListener getMessengerListener = new EndpointGetMessengerAsyncListener(this, dest);

                boolean stat = endpoint.getMessenger(getMessengerListener, new EndpointAddress(addr, routerSName, null), null);

                if (stat == false) {
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("failed creating async messenger, continue");
                    }
                    // we failed to get a messenger, we need to update the try and
                    // failed as it currently holds an infinite timeout to permit
                    // another thread to retry that destination. We only retry
                    // every MAXASYNC_GETMESSENGER_RETRY seconds
                    synchronized (this) {
                        triedAndFailed.put(dest, new Long(TimeUtils.toAbsoluteTimeMillis(MAXASYNC_GETMESSENGER_RETRY)));
                    }
                    continue;
                }

                // wait to see if we can get the Async messenger
                // If there is a long route to that destination, do not
                // wait on the direct route.
                // It may happen that we are actually
                // trying to reach a different peer and this is just part of
                // shortcuting the route via the one of the hops. In that case
                // this test is not entirely accurate. We might still decide
                // to wait when we shouldn't (we're no worse than before, then)
                // But, in most cases, this is going to help.
                boolean quick = (getRoute(dest, false) != null);

                messenger = getMessengerListener.waitForMessenger(quick);
                if (messenger == null) {
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("did not get our async messenger, bail out");
                    }
                    continue;
                } else {
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("we got our async messenger, proceed");
                    }

                    // Success we got a messenger synchronously. Remove
                    // the negative cache entry.
                    synchronized (this) {
                        triedAndFailed.remove(dest);
                        notifyAll();
                    }

                    return messenger;
                }
            } catch (Throwable e) {
                // That address is somehow broken.
                // Cache that result for a while.
                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug("getBestLocalRoute - failed checking route : " + addr, e);
                }
            }
        }

        return null;
    }

    /**
     *  Read the route advertisement for a peer and find a suitable transport
     *  endpoint for sending to that peere either directly or via one of
     *   the advertised peer router
     */
    Messenger findReachableEndpoint(EndpointAddress destPeer, boolean exist) {
        return  findReachableEndpoint(destPeer, exist, null);
    }

    Messenger findReachableEndpoint(EndpointAddress destPeer, boolean exist, Object hint) {

        // findEndpoint is really lazy because what it does is expensive.
        // When needed, the negative info that prevents its from working
        // too much is removed. (see calls to ensureLocalRoute).
        synchronized (this) {
            Long nextTry = (Long) triedAndFailed.get(destPeer);

            if (nextTry != null) {
                if (nextTry.longValue() > TimeUtils.timeNow()) {
                    return null;
                }
            }
            // We are the first thread trying this destination.
            // Let's preclude any other threads from attempting to do
            // anything while we are trying that destination. Other
            // threads will have a chance if they are still waiting
            // when this thread is done. We will update triedAndFailed
            // when we get the async notification that we got or we
            // failed to get a messenger.
            triedAndFailed.put(destPeer, new Long(TimeUtils.toAbsoluteTimeMillis(Long.MAX_VALUE)));

        }

        // Never tried or it was a long time ago.

        // Get (locally) the advertisements of this peer
        Iterator advs = null;

        try {
            // try to use the hint that was given to us
            if (hint != null) {
                advs = Collections.singletonList(hint).iterator();
            }

            // Check if we got any advertisements
            List addrs = new ArrayList();

            while (advs!= null && advs.hasNext()) {
                RouteAdvertisement adv = (RouteAdvertisement) advs.next();

                String saddr = null;

                // add the destination endpoint
                for (Enumeration e = adv.getDest().getEndpointAddresses(); e.hasMoreElements();) {
                    try {
                        saddr = (String) e.nextElement();
                        addrs.add(new EndpointAddress(saddr));
                    } catch (Throwable ex) {
                        if (LOG.isEnabledFor(Level.DEBUG)) {
                            LOG.debug(" bad address in route adv : " + saddr);
                        }
                    }
                }
            }

            // ok let's go and try all these addresses
            if (!addrs.isEmpty()) {
                Messenger bestMessenger = findBestReachableEndpoint(destPeer, addrs, exist);

                if (bestMessenger != null) {

                    // Found a direct route. Return it.
                    // Tried+failed has been cleaned.
                    if (LOG.isEnabledFor(Level.DEBUG)) {
                        LOG.debug("found direct route");
                    }
                    return bestMessenger;
                }
            }

            // We're done trying. Since we did not find anything at all,
            // the triedFailed record is still set to infinite value.
            // Reset it to finite.
            // There is a small chance that another thread did find
            // something in parallel, but that's very unlikely and
            // if it is rare enough then the damage is small.
            synchronized (this) {
                triedAndFailed.put(destPeer, new Long(TimeUtils.toAbsoluteTimeMillis(MAXASYNC_GETMESSENGER_RETRY)));
            }
        } catch (Throwable e) {
            // If something weird happened be conservative and set a standard
            // finite timeout.
            synchronized (this) {
                triedAndFailed.put(destPeer, new Long(TimeUtils.toAbsoluteTimeMillis(MAXASYNC_GETMESSENGER_RETRY)));
            }
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.warn("error looking for an address ", e);
            }
        }

        if (LOG.isEnabledFor(Level.DEBUG)) {
            LOG.debug("did not find a direct route to :" + destPeer);
        }

        return null;
    }

    /**
     *  {@inheritDoc}
     */
    public Messenger getMessenger(EndpointAddress addr, Object hint) {

        RouteAdvertisement routeHint = null;
        EndpointAddress plainAddr = new EndpointAddress(addr, null, null);

        // If the dest is the local peer, just loop it back without going
        // through the router.
        if (plainAddr.equals(localPeerAddr)) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("return LoopbackMessenger");
            }
            return new LoopbackMessenger(endpoint, localPeerAddr, addr, addr);
        }

        try {
            // try and add that hint to our cache of routes (that may be our only route).
            if (hint != null && hint instanceof RouteAdvertisement) {
                routeHint = (RouteAdvertisement) ((RouteAdvertisement) hint).clone();
                AccessPointAdvertisement firstHop = routeHint.getFirstHop();
                PeerID firstHopPid = null;
                EndpointAddress firstHopAddr = null;

                // If the firstHop is equal to the destination, clean that up,
                // that's a direct route. If the first hop is the local peer
                // leave it there but treat it as a local route. That's what
                // it is from the local peer point of view.
                if (firstHop != null) {

                    firstHopPid = firstHop.getPeerID();
                    firstHopAddr = pid2addr(firstHopPid);

                    if (firstHopAddr.equals(addr)) {
                        routeHint.removeHop(firstHopPid);
                        firstHop = null;
                    } else if (firstHopPid.equals(localPeerId)) {
                        firstHop = null;
                    }

                }

                if (firstHop != null) {
                    // For the hint to be usefull, we must actively try the first hop.
                    // It is possible that we do not know it yet and that's not a reason
                    // to ignore the hint (would ruin the purpose in most cases).
                    RouteAdvertisement routeFirstHop = null;

                    // Manufacture a RA just that as just the routerPeer
                    // as a destination.
                    // we only need to publish this route if
                    // we don't know about it yet.
                    if (!(isLocalRoute(firstHopAddr) || routedRoutes.containsKey(firstHopAddr))) {

                        routeFirstHop = (RouteAdvertisement)
                                        AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType());
                        routeFirstHop.setDest((AccessPointAdvertisement) firstHop.clone());

                        // Here we used to pass a second argument with
                        // value true which forced updateRouteAdv to
                        // ignore a pre-existing identical adv and
                        // remove negative cache information anyway.
                        // The reason for doing that was that
                        // sometimes the new route adv does already
                        // exist but has not yet been tried. We cannot
                        // do that; it exposes us too much to retrying
                        // incessantly the same address. A hint cannot
                        // be trusted to such an extent. The correct
                        // remedy is to be able to tell accurately if
                        // there really is an untried address in that
                        // radv, which requires a sizeable refactoring.
                        // in the meantime just let the negative cache
                        // play its role.
                        updateRouteAdv(routeFirstHop);
                    }

                    // if we constructed the route hint then passes it
                    // in the past we were just relying on the CM
                    // now that the CM can be disabled, we have to
                    // pass the argument.
                    if (ensureLocalRoute(firstHopAddr, routeFirstHop) != null) {
                        setRoute((RouteAdvertisement) routeHint.clone(), false);
                    }
                }
            }

        } catch (Throwable ioe) {
            // Enforce a stronger semantic to hint. If the application passes
            // a hint that is rotten then this is an application problem
            // we should not try to fix what was given to us.
            return null;
        }

        try {
            // Build a persistent RouterMessenger around it that will add our header.
            // If a hint was passed to us we just use it as it. To bad if it is not the
            // the right one. In that mode it is the responsability of the application
            // to make sure that a correct hint was passed
            return new RouterMessenger(getPublicAddress(), addr, this, routeHint);
        } catch (IOException caught) {
            if (LOG.isEnabledFor(Level.DEBUG)) {
                LOG.debug("getMessenger: can't generate messenger for addr " + addr + " " + caught.getMessage());
            }
            return null;
        }
    }

    /**
     * Updates the router element of a message and returns the pid address of the next
     * hop (where to send the message).
     * Currently, address message is only called for messages that we originate. As a result
     * we will always agressively seek a route if needed.
     * @param message the message for which to compute/update a route.
     * @param destAddress the final destination of the route which the message be set to follow.
     * @return EndpointAddress The address (logical) where to send the message next. Null if there
     * is nowhere to send it to.
     */

    EndpointAddress addressMessage(Message message, EndpointAddress dstAddress) {
        if (endpoint == null) {
            return null;
        }

        // We need to create a RouterMessage
        if (LOG.isEnabledFor(Level.DEBUG)) {
            LOG.debug("Create a new EndpointRouterMessage " + dstAddress);
        }

        // Specify that we do not want an existing msg parsed.
        EndpointRouterMessage routerMsg = new EndpointRouterMessage(message, true);

        if (routerMsg.isDirty()) {

            // Oops there was one in the message already. This must be
            // a low-level protocol looping back through the
            // router. The relay can be led to do that in some corner
            // cases
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.warn("Possible transport recursion");
            }
            throw new
            IllegalTransportLoopException("RouterMessage element already present");
        }

        routerMsg.setSrcAddress(localPeerAddr);
        routerMsg.setDestAddress(dstAddress);

        EndpointAddress theGatewayAddress = null;
        EndpointAddress dstAddressPlain = null;

        try {
            RouteAdvertisement route = null;

            theGatewayAddress = getGatewayAddress(dstAddress, true);

            if (theGatewayAddress == null) {
                // Cleanup the message, so that the invoker
                // may retry (with a different hint, for example).
                routerMsg.clearAll();
                routerMsg.updateMessage();
                return null;
            }

            dstAddressPlain = new EndpointAddress(dstAddress, null, null);

            // Check that we're actually going through a route; we could have one
            // but not be using it, because we know of a volatile shortcut.

            // FIXME: jice@jxta.org - 20030512: This is not very clean:
            // getGatewayAddress should be giving us the route that it's using, if any.
            // By doing the fetch ourselves, not only do we waste CPU hashing
            // twice, but we could also get a different route !

            if (!theGatewayAddress.equals(dstAddressPlain)) {
                route = getRoute(dstAddressPlain, false);
            }

            // If we're going through a route for that, stuff it in the
            // message. NB: setForwardHops does not clone.
            if (route != null) {
                routerMsg.setForwardHops((Vector)
                                         route.getVectorHops().clone());
            }

            // set the last hop info to point to the local peer info
            // The recipient takes last hop to be the last peer that the message has traversed
            // before arriving.
            routerMsg.setLastHop(localPeerAddr.toString());

            // The first time we talk to a peer to which we have
            // initiated a connection, we must include our local
            // route adv in the routerMsg.
            RouteAdvertisement myRoute = getMyLocalRoute();

            if (myRoute != null) {
                // FIXME - jice@jxta.org 20040430 : use destinations instead of newDestinations, even for routed ones.
                boolean newDest = newDestinations.remove(dstAddressPlain);
                boolean newGatw = destinations.isWelcomeNeeded(theGatewayAddress);

                if (newDest || newGatw) {
                    routerMsg.setRouteAdv(myRoute);
                }
            }

            // Push the router header onto the message.
            // That's all we have to do for now.

            routerMsg.updateMessage();

        } catch (Exception ez1) {
            // Not much we can do
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.warn("Could not fully address message", ez1);
            }
            return null;
        }

        return theGatewayAddress;
    }

    /**
     *  {@inheritDoc}
     */
    public Object transportControl(Object operation, Object value) {
        if (!(operation instanceof Integer)) {
            return null;
        }

        int op = ((Integer) operation).intValue();

        switch (op) {

        case RouteControlOp: // Add a new Route
            return new RouteControl(this, localPeerId);

        default:
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.debug("Invalid Transport Control operation argument");
            }

            return null;
        }
    }

    /**
     * convert an endpointRouterAddress into a PeerID
     */
    protected static PeerID addr2pid(EndpointAddress addr) {
        try {
            URI asURI = new URI(ID.URIEncodingName, ID.URNNamespace + ":" + addr.getProtocolAddress(), null);

            return (PeerID) IDFactory.fromURI(asURI);
        } catch (URISyntaxException ex) {
            return null;
        }
    }

    /**
     * convert a PeerID into an EndpointRouter Address
     */
    protected static EndpointAddress pid2addr(PeerID pid) {
        return new EndpointAddress(routerPName, pid.getUniqueValue().toString(), null, null);
    }

    /**
     * check if it is a new route adv
     */
    protected void updateRouteAdv(RouteAdvertisement route) {
        updateRouteAdv(route, false);
    }

    /**
     * check if it is a new route adv
     */
    protected void updateRouteAdv(RouteAdvertisement route, boolean force) {
        try {
            PeerID pID = route.getDestPeerID();
                if (LOG.isEnabledFor(Level.DEBUG)) {
                    LOG.debug("Route for " + pID + " is same as existing route, not publishing it");
                }
                if (force) {
                    synchronized (this) {
                        Long nextTry = (Long) triedAndFailed.get(pid2addr(pID));

                        if (nextTry != null) {
                            // only remove if we do not have a pending request (infinite retry)
                            // we take the conservative approach to avoid creating multiple
                            // async thread blocked on the same destination
                            if (nextTry.longValue() <= TimeUtils.toAbsoluteTimeMillis(MAXASYNC_GETMESSENGER_RETRY)) {
                                triedAndFailed.remove(pid2addr(pID));
                                notifyAll();
                            }
                        }
                    }
                }
        } catch (Exception e) {
            if (LOG.isEnabledFor(Level.WARN)) {
                LOG.warn("Failed to publish route advertisement", e);
            }
        }
    }

    /**
     * is there a pending route query for that destination
     *
     * @param addr destination address
     *
     * @return true or false
     */
    protected synchronized boolean isPendingRouteQuery(EndpointAddress addr) {
        return pendingQueries.containsKey(addr);
    }

    /**
     * get a pending route query info
     *
     * @param addr destination address
     *
     * @return pending route query info
     */
    protected synchronized ClearPendingQuery getPendingRouteQuery(EndpointAddress addr) {
        return (ClearPendingQuery) pendingQueries.get(addr);
    }

    /**
     * Do we have a longue route for that destination
     *
     * @param addr destination address
     *
     * @return true or false
     */
    protected boolean isRoutedRoute(EndpointAddress addr) {
        return routedRoutes.containsKey(addr);
    }

    /**
     * Snoop if we have a messenger
     *
     * @param addr destination address
     *
     * @return Messenger
     */
    protected Messenger getCachedMessenger(EndpointAddress addr) {
        return destinations.getCurrentMessenger(addr);
    }

    /**
     * Get all direct route destinations
     *
     * @return Iterator iterations of all endpoint destinations
     */
    protected Iterator getAllCachedMessengerDestinations() {
        return  destinations.allDestinations().iterator();
    }

    /**
     * Get all long route destinations
     *
     * @return Iterator iterations of all routed route destinations
     */
    protected Iterator getRoutedRouteAllDestinations() {
        return routedRoutes.entrySet().iterator();
    }

    /**
     * Get all long route destination addresses
     *
     * @return Iterator iterations of all routed route addresses
     */
    protected synchronized Iterator getAllRoutedRouteAddresses() {
        // XXX 20041107 bondolo Being synchronized does not make this itertor safe.
        return routedRoutes.keySet().iterator();
    }

    /**
     * Get all pendingRouteQuery destinations
     *
     * @return Iterator iterations of all pending route query destinations
     */
    protected Iterator getPendingQueriesAllDestinations() {
        return pendingQueries.entrySet().iterator();
    }

    /**
     * FIXME add route resolver later on
     * Get the route resolver manager
    protected RouteResolver getRouteResolver() {
        return routeResolver;
    }
     */

    /**
     * set bad route entry
     *
     * @param addr of the bad route
     * @param badRoute bad route info
     */
    protected synchronized void setBadRoute(EndpointAddress addr, BadRoute badRoute) {
        badRoutes.put(addr, badRoute);
    }

    /**
     * get bad route entry
     *
     * @param addr of the bad route
     * @return BadRoute bad route info
     */
    protected synchronized BadRoute  getBadRoute(EndpointAddress addr) {
        return (BadRoute) badRoutes.get(addr);
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.