/* * Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved. * * The Sun Project JXTA(TM) Software License * * 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 Sun Microsystems, Inc. for JXTA(TM) technology." * 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. * * JXTA is a registered trademark of Sun Microsystems, Inc. in the United * States and other countries. * * Please see the license information page at : * <http://www.jxta.org/project/www/license.html> for instructions on use of * the license in source files. * * ==================================================================== * * 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.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; 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.Vector; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import net.jxta.document.Advertisement; import net.jxta.document.AdvertisementFactory; import net.jxta.document.XMLElement; 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.exception.PeerGroupException; import net.jxta.id.ID; import net.jxta.id.IDFactory; import net.jxta.impl.endpoint.LoopbackMessenger; import net.jxta.impl.util.TimeUtils; import net.jxta.impl.util.threads.SelfCancellingTask; import net.jxta.impl.util.threads.TaskManager; import net.jxta.logging.Logging; import net.jxta.peer.PeerID; import net.jxta.peergroup.PeerGroup; import net.jxta.platform.Module; import net.jxta.protocol.AccessPointAdvertisement; import net.jxta.protocol.ModuleImplAdvertisement; import net.jxta.protocol.PeerAdvertisement; import net.jxta.protocol.RouteAdvertisement; import net.jxta.service.Service; public class EndpointRouter implements EndpointListener, MessageReceiver, MessageSender, MessengerEventListener, Module { /** * Logger */ private final static transient Logger LOG = Logger.getLogger(EndpointRouter.class.getName()); /** * Until we decide otherwise, the router is *by definition* handling * virtually addressed messages. */ private final static String ROUTER_PROTOCOL_NAME = "jxta"; /** * Router Service Name */ private final static String ROUTER_SERVICE_NAME = "EndpointRouter"; /** * 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; /** * MessageTransport Control operation */ public final static Integer GET_ROUTE_CONTROL = 0; // Return RouteControl Object public final static int RouteControlOp = 0; // Return RouteControl Object /** * MAX timeout (seconds) for route discovery after that timeout * the peer will bail out from finding a route */ private final static long MAX_FINDROUTE_TIMEOUT = 60L * TimeUtils.ASECOND; /** * How long do we wait (seconds) before retrying to make a connection * to an endpoint destination */ private final static long MAX_ASYNC_GETMESSENGER_RETRY = 30L * TimeUtils.ASECOND; /** * These are peers which we know multi-hop routes for. */ private final Map<ID, RouteAdvertisement> routedRoutes = new HashMap<ID, RouteAdvertisement>(16); /** * A record of failures. * <p/> * Values are the time of failure as {@link java.lang.Long}. If * {@code Long.MAX_VALUE} then a connect attempt is current in progress. */ private final Map<PeerID, Long> triedAndFailed = new HashMap<PeerID, Long>(); /** * local peer ID as an endpointAddress. */ private EndpointAddress localPeerAddr = null; /** * local Peer ID */ private PeerID localPeerId = null; /** * The Endpoint Service we are routing for. */ private EndpointService endpoint = null; /** * PeerGroup handle */ private PeerGroup group = null; private ID assignedID = null; /** * If {@code true} this service has been closed. */ private boolean stopped = false; /** * 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. */ private final Set<EndpointAddress> newDestinations = Collections.synchronizedSet(new HashSet<EndpointAddress>()); /** * 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 */ private final Map<EndpointAddress, BadRoute> badRoutes = new HashMap<EndpointAddress, BadRoute>(); /** * 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. * <p/> * 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). * <p/> * FIXME: jice@jxta.org 20020903 this is approximate. We can do * cleaner/better than this, but it's already an inexpensive improvement * over what used before. * <p/> * FIXME: tra@jxta.org 20030818 the pending hashmap should be moved * in the routeResolver class. */ private final Map<PeerID, ClearPendingQuery> pendingQueries = Collections.synchronizedMap(new HashMap<PeerID, ClearPendingQuery>()); /** * Timer by which we schedule the clearing of pending queries. */ private final ScheduledExecutorService timer = TaskManager.getTaskManager().getScheduledExecutorService(); /** * 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 embed 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 CM persistent cache */ private RouteCM routeCM = null; /** * Route Resolver */ private RouteResolver routeResolver; class ClearPendingQuery extends SelfCancellingTask { final PeerID peerID; volatile boolean failed = false; long nextRouteResolveAt = 0; ClearPendingQuery(PeerID peerID) { this.peerID = peerID; // We schedule for one tick at one minute and another at 5 minutes // after the second, we cancel ourselves. setHandle(timer.scheduleAtFixedRate(this, 60, 60 * 5, TimeUnit.SECONDS)); nextRouteResolveAt = TimeUtils.toAbsoluteTimeMillis(20L * TimeUtils.ASECOND); } /** * {@inheritDoc} */ @Override public void execute() { try { if (failed) { // Second tick. // This negative cache info is expired. pendingQueries.remove(peerID); 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 (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.log(Level.SEVERE, "Uncaught Throwable in timer task " + Thread.currentThread().getName() + " for " + peerID, all); } } } public synchronized boolean isTimeToResolveRoute() { if (TimeUtils.toRelativeTimeMillis(nextRouteResolveAt) > 0) { return false; } // nextRouteResolveAt is passed. Set the next time to retry from now. nextRouteResolveAt = TimeUtils.toAbsoluteTimeMillis(20L * TimeUtils.ASECOND); return true; } public boolean isFailed() { return failed; } } 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 XMLElement endpParam = (XMLElement) newPadv.getServiceParam(PeerGroup.endpointClassID); if (endpParam == null) { if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.severe("no Endpoint SVC Params"); } // Return whatever we had so far. return localRoute; } // get the Route Advertisement element Enumeration paramChilds = endpParam.getChildren(RouteAdvertisement.getAdvertisementType()); XMLElement param; if (paramChilds.hasMoreElements()) { param = (XMLElement) paramChilds.nextElement(); } else { if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.severe("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(param); route.setDestPeerID(localPeerId); localRoute = route; } catch (Exception ex) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Failure extracting route", ex); } } return localRoute; } /** * listener object to synchronize on asynchronous getMessenger */ private static class EndpointGetMessengerAsyncListener implements MessengerEventListener { private final EndpointRouter router; private final EndpointAddress logDest; volatile boolean hasResponse = false; volatile boolean isGone = false; private Messenger messenger = null; /** * Constructor * * @param router the router * @param dest logical destination */ EndpointGetMessengerAsyncListener(EndpointRouter router, EndpointAddress dest) { this.router = router; this.logDest = dest; } /** * {@inheritDoc} */ public boolean messengerReady(MessengerEvent event) { Messenger toClose = null; synchronized (this) { hasResponse = true; if (event != null) { messenger = event.getMessenger(); if (null != messenger) { if (!logDest.equals(messenger.getLogicalDestinationAddress())) { // Ooops, wrong number ! if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Incorrect Messenger logical destination : " + logDest + "!=" + messenger.getLogicalDestinationAddress()); } toClose = messenger; messenger = null; } } else { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("null messenger for dest :" + logDest); } } } else { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("null messenger event for dest :" + logDest); } } } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { if (messenger == null) { LOG.fine("error creating messenger for dest :" + logDest); } else { LOG.fine("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 MAX_ASYNC_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 (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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 * * @param quick if true return a messenger immediately if available, * otherwise wait the Messenger resolution to be completed * @return the Messenger if one available */ 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; } } /** * isLocalRoute is a shallow test. It tells you that there used to be a * local route that worked the last time it was tried. * * @param peerAddress Address of the destination who's route is desired. * @return {@code true} if we know a direct route to the specified address * otherwise {@code false}. */ boolean isLocalRoute(EndpointAddress peerAddress) { return destinations.isCurrentlyReachable(peerAddress); } /** * Get a Messenger for the specified destination if a direct route is known. * * @param peerAddress The peer who's messenger is desired. * @param hint A route hint to use if a new Messenger must be created. * @return Messenger for direct route or {@code null} if none could be * found or created. */ Messenger ensureLocalRoute(EndpointAddress peerAddress, RouteAdvertisement 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 activly try to make // one. // See if we already have a messenger. Messenger messenger = destinations.getCurrentMessenger(peerAddress); if (messenger != null) { return messenger; } // Ok, try and make one. Pass the route hint info messenger = findReachableEndpoint(peerAddress, false, hint); if (messenger == null) { // We must also zap it from our positive cache: if we remembered it // working, we should think again. destinations.noOutgoingMessenger(peerAddress); return null; // No way. } destinations.addOutgoingMessenger(peerAddress, messenger); // 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 messenger; } /** * 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. * @throws java.io.IOException if an io error occurs */ void sendOnLocalRoute(EndpointAddress destination, Message message) throws IOException { IOException lastIoe = null; Messenger sendVia; // Try sending the message as long as we get have transport messengers // 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 ((sendVia = ensureLocalRoute(destination, null)) != null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Sending " + message + " to " + destination + " via " + sendVia); } try { // FIXME 20040413 jice Maybe 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). sendVia.sendMessageB(message, EndpointRouter.ROUTER_SERVICE_NAME, null); // If we reached that point, we're done. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Sent " + message + " to " + destination); } return; } catch (IOException ioe) { // Can try again, with another messenger (most likely). lastIoe = ioe; } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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 reachable endpoints for " + destination); } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Could not send to " + destination, lastIoe); } throw lastIoe; } /** * Default constructor */ public EndpointRouter() { } /** * {@inheritDoc} */ public void init(PeerGroup group, ID assignedID, Advertisement impl) throws PeerGroupException { this.group = group; this.assignedID = assignedID; ModuleImplAdvertisement implAdvertisement = (ModuleImplAdvertisement) impl; localPeerId = group.getPeerID(); localPeerAddr = pid2addr(group.getPeerID()); if (Logging.SHOW_CONFIG && LOG.isLoggable(Level.CONFIG)) { StringBuilder configInfo = new StringBuilder("Configuring Router Transport : " + assignedID); if (implAdvertisement != null) { configInfo.append("\n\tImplementation :"); configInfo.append("\n\t\tModule Spec ID: ").append(implAdvertisement.getModuleSpecID()); configInfo.append("\n\t\tImpl Description : ").append(implAdvertisement.getDescription()); configInfo.append("\n\t\tImpl URI : ").append(implAdvertisement.getUri()); configInfo.append("\n\t\tImpl Code : ").append(implAdvertisement.getCode()); } configInfo.append("\n\tGroup Params :"); configInfo.append("\n\t\tGroup : ").append(group); configInfo.append("\n\t\tPeer ID : ").append(group.getPeerID()); configInfo.append("\n\tConfiguration :"); configInfo.append("\n\t\tProtocol : ").append(getProtocolName()); configInfo.append("\n\t\tPublic Address : ").append(localPeerAddr); LOG.config(configInfo.toString()); } } /** * {@inheritDoc} */ public synchronized int startApp(String[] arg) { endpoint = group.getEndpointService(); if (null == endpoint) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Stalled until there is an endpoint service"); } return START_AGAIN_STALLED; } Service needed = group.getResolverService(); if (null == needed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Endpoint Router start stalled until resolver service available"); } return Module.START_AGAIN_STALLED; } needed = group.getMembershipService(); if (null == needed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Endpoint Router start stalled until membership service available"); } return Module.START_AGAIN_STALLED; } needed = group.getRendezVousService(); if (null == needed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Endpoint Router start stalled until rendezvous service available"); } return Module.START_AGAIN_STALLED; } destinations = new Destinations(endpoint); try { // FIXME tra 20030818 Should be loaded as a service // when we have service dependency. 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. routeCM = new RouteCM(); // FIXME tra 20030818 Should be loaded as a service // when we have service dependency. 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. routeResolver = new RouteResolver(this); // initialize persistent CM route Cache // FIXME tra 20030818 Should be loaded as service when complete // refactoring is done. routeCM.init(group, assignedID, null); // initialize the route resolver // FIXME tra 20030818 Should be loaded as service when complete // refactoring is done. routeResolver.init(group, assignedID, null); } catch (PeerGroupException failure) { return -1; } int status; // FIXME tra 20031015 Should be started as a service when refactored work // completed status = routeCM.startApp(arg); if (status != 0) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Route CM failed to start : " + status); } return status; } // FIXME tra 20031015 Should be started as a service when refactored work // completed status = routeResolver.startApp(arg); if (status != 0) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Route Resolver failed to start : " + status); } return status; } // publish my local route adv routeCM.publishRoute(getMyLocalRoute()); // FIXME tra 20031015 is there a risk for double registration when // startApp() is recalled due to failure to get the discovery service // by the Route Resolver service. // 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); endpoint.addIncomingMessageListener(this, ROUTER_SERVICE_NAME, null); if (endpoint.addMessageTransport(this) == null) { if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.severe("Transport registration refused"); } return -1; } if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) { LOG.info(group + " : Router Message Transport started."); } return status; } /** * {@inheritDoc} * <p/> * Careful that stopApp() could in theory be called before startApp(). */ public synchronized void stopApp() { stopped = true; if (endpoint != null) { endpoint.removeIncomingMessageListener(ROUTER_SERVICE_NAME, null); endpoint.removeMessengerEventListener(this, EndpointService.MediumPrecedence); endpoint.removeMessageTransport(this); } // FIXME tra 20030818 should be unloaded as a service routeCM.stopApp(); // FIXME tra 20030818 should be unloaded as a service routeResolver.stopApp(); destinations.close(); if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) { LOG.info(group + " : 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 localPeerAddr; } /** * {@inheritDoc} */ public Iterator<EndpointAddress> getPublicAddresses() { return Collections.singletonList(getPublicAddress()).iterator(); } /** * {@inheritDoc} */ public String getProtocolName() { return ROUTER_PROTOCOL_NAME; } /** * 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 peerAddress 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 peerAddress, boolean seekRoute, RouteAdvertisement hint) { PeerID peerID = addr2pid(peerAddress); try { // FIXME 20021215 jice Replace this junk with a background task; // separate the timings of route disco from the timeouts of // the requesting threads. EndpointAddress result = null; if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Searching local" + (seekRoute ? " & remote" : "") + " for route for " + peerAddress); } // If we can't get a route within the timeout, give up for now. long quitAt = TimeUtils.toAbsoluteTimeMillis(MAX_FINDROUTE_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. Messenger directMessenger = ensureLocalRoute(peerAddress, hint); if (null != directMessenger) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Found direct route for " + peerAddress + " via " + directMessenger.getDestinationAddress()); } return peerAddress; } // Otherwise, look for a long route. // check if we got a hint. If that's the case use it RouteAdvertisement route; if (hint != null) { route = hint; addr = extractUsableAddress(peerAddress, route); if(addr != null){ return addr; } } // the hint that we got was useless or we did not get any hint route = getRoute(peerAddress, seekRoute); if (route != null && route.size() > 0) { addr = extractUsableAddress(peerAddress, route); if (addr != null) { return addr; } else { removeRoute(peerID); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Found no reachable route to " + peerAddress); } } } // 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. 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 = triedAndFailed.get(peerID); if ((nextTry == null) || (nextTry < TimeUtils.toAbsoluteTimeMillis(MAX_ASYNC_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; synchronized (pendingQueries) { t = pendingQueries.get(peerID); if (t == null) { doFind = true; t = new ClearPendingQuery(peerID); pendingQueries.put(peerID, t); } else { if (t.isFailed()) { break; } if (t.isTimeToResolveRoute()) { 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(peerAddress); // 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(peerAddress) == null) { wait(ASYNC_MESSENGER_WAIT); } } catch (InterruptedException woken) { Thread.interrupted(); } } } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("No route to " + peerAddress); } return null; } catch (Exception ex) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "getGatewayAddress exception", ex); } return null; } } private EndpointAddress extractUsableAddress(EndpointAddress peerAddress, RouteAdvertisement route) { EndpointAddress addr = null; if (route != null && route.size() > 0) { addr = pid2addr(route.getLastHop().getPeerID()); if (ensureLocalRoute(addr, null) != null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Found last hop remote address: " + peerAddress + " -> " + route.getLastHop().getPeerID()); } // Ensure local route removes negative cache info about // addr. We also need to remove that about peerAddress. } else { // need to try the first hop addr = pid2addr(route.getFirstHop().getPeerID()); if (ensureLocalRoute(addr, null) != null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Found first hop remote address first hop: " + peerAddress + " -> " + route.getFirstHop().getPeerID()); } // Ensure local route removes negative cache info about addr. } else { // nullify the result - we did not find anythig usable addr = null; } } } return addr; } /** * 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 (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Ignoring messenger to :" + logDest); } return false; } // We learned that a transport messenger has just been announced. // Nobody 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 */ 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. PeerID peerID = addr2pid(logDest); synchronized (this) { Long curr = triedAndFailed.get(peerID); if (curr != null && curr > TimeUtils.toAbsoluteTimeMillis(MAX_ASYNC_GETMESSENGER_RETRY)) { triedAndFailed.put(peerID, TimeUtils.toAbsoluteTimeMillis(MAX_ASYNC_GETMESSENGER_RETRY)); } } } /** * Call when an asynchronous new messenger is ready. * (name is not great). * * @param event the new messenger event. * @return always returns true */ 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 peerAddress 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. */ RouteAdvertisement getRoute(EndpointAddress peerAddress, boolean seekRoute) { ID peerID = addr2pid(peerAddress); // check if we have a valid route RouteAdvertisement route; synchronized (this) { route = routedRoutes.get(peerID); } if (route != null || !seekRoute) { // done return route; } // No known route and we're allowed to search for one // check if there is route advertisement available Iterator<RouteAdvertisement> allRadvs = routeCM.getRouteAdv(peerID); while (allRadvs.hasNext()) { route = allRadvs.next(); Vector<AccessPointAdvertisement> hops = route.getVectorHops(); // no hops, uninterresting: this needs to be a route if (hops.isEmpty()) { continue; } // let's check if we can speak to any of the hops in the route // we try them in reverse order so we shortcut the route // in the process RouteAdvertisement newRoute = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); newRoute.setDest(route.getDest().clone()); Vector<AccessPointAdvertisement> newHops = new Vector<AccessPointAdvertisement>(); // build the route from the available hops for (int i = hops.size() - 1; i >= 0; i--) { ID hopID = hops.elementAt(i).getPeerID(); // If the local peer is one of the first reachable // hop in the route, that route is worthless to us. if (localPeerId.equals(hopID)) { break; } EndpointAddress addr = pid2addr(hopID); if (ensureLocalRoute(addr, null) != null) { // we found a valid hop return the corresponding // route from that point for (int j = i; j <= hops.size() - 1; j++) { newHops.add(hops.elementAt(j).clone()); } // make sure we have a real route at the end if (newHops.isEmpty()) { break; } newRoute.setHops(newHops); // try to set the route if (setRoute(newRoute, false)) { // We got one; we're done. return newRoute; } else { // For some reason the route table does not // want that route. Move on to the next adv; it // unlikely that a longer version of the same would // be found good. break; } } } } // no route found return null; } /** * Check if a route is valid. * Currently, only loops are detected. * * @param routeAdvertisement The route to check. * @return {@code true} if the route is valid otherwise {@code false}. */ private boolean checkRoute(RouteAdvertisement routeAdvertisement) { if (0 == routeAdvertisement.size()) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("route is empty"); } return false; } if (routeAdvertisement.containsHop(localPeerId)) { // The route does contain this local peer. Using this route // would create a loop. Discard. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("route contains this peer - loopback"); } return false; } PeerID destPid = routeAdvertisement.getDest().getPeerID(); if (routeAdvertisement.containsHop(destPid)) { // May be it is fixable, may be not. See to it. Vector<AccessPointAdvertisement> hops = routeAdvertisement.getVectorHops(); // It better be the last hop. Else this is a broken route. hops.remove(hops.lastElement()); if (routeAdvertisement.containsHop(destPid)) { // There was one other than last: broken route. return false; } } if (routeAdvertisement.hasALoop()) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("route has a loop "); } return false; } else { // Seems to be a potential good route. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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 route new route to learn * @param force true if the route was obtained by receiving * a message * @return true if route was truly new */ boolean setRoute(RouteAdvertisement route, boolean force) { PeerID peerID; EndpointAddress peerAddress; boolean pushNeeded = false; boolean status; if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("setRoute:"); } if (route == null) { return false; } synchronized (this) { try { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine(route.display()); } peerID = route.getDest().getPeerID(); peerAddress = pid2addr(peerID); // 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 = badRoutes.get(peerAddress); if (badRoute != null) { Long nextTry = badRoute.getExpiration(); if (nextTry > 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 = route.cloneOnlyPIDs(); if (routeClean.equals(badRoute.getRoute())) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("try to use a known bad route"); } return false; } } else { // expired info, just flush NACK route cache badRoutes.remove(peerAddress); } } } else { // we get a new route badRoutes.remove(peerAddress); } // Check if the route makes senses (loop detection) if (!checkRoute(route)) { // Route is invalid. Drop it. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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(route.getFirstHop().getPeerID()))) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Unreachable route - ignore"); } return false; } } catch (Exception ez1) { // The vector must be empty, which is not supposed to happen. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Got an empty route - discard" + route.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(peerID)) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("push new SRDI route " + peerID); } pushNeeded = true; } } // new route so publish the known route in our cache if (!routedRoutes.containsKey(peerID)) { routeCM.createRoute(route); newDestinations.add(peerAddress); } // Remove any endpoint addresses from the route // as part of the cloning. We just keep track // of PIDs in our route table RouteAdvertisement newRoute = route.cloneOnlyPIDs(); routedRoutes.put(peerID, newRoute); // We can get rid of any negative info we had. We have // a new and different route. badRoutes.remove(peerAddress); notifyAll(); // Wakeup those waiting for a route. status = true; } catch (Exception e2) { // We failed, leave things as they are. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine(" failed setting route with " + e2); } status = false; } } // due to the potential high latency of making the // srdi revolver push we don't want to hold the lock // on the EndpointRouter object as we may have to // discover a new route to a rendezvous if (pushNeeded && status) { // we are pushing the SRDI entry to a replica peer routeResolver.pushSrdi(null, peerID); } return status; } /** * This method is used to remove a route * * @param peerID route to peerid to be removed */ void removeRoute(PeerID peerID) { boolean needRemove; synchronized (this) { needRemove = false; if (routedRoutes.containsKey(peerID)) { if (group.isRendezvous()) { // Remove the SRDI cache entry from the SRDI cache needRemove = true; if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("remove SRDI route " + peerID); } } routedRoutes.remove(peerID); } } // due to the potential high latency of pushing // the SRDI message we don't want to hold the EndpointRouter // object lock if (needRemove) { // We are trying to flush it from the replica peer // Note: this is not guarantee to work if the peerview // is out of sync. The SRDI index will be locally // repaired as the peerview converge routeResolver.removeSrdi(null, peerID); } } /** * {@inheritDoc} */ public void processIncomingMessage(Message msg, EndpointAddress srcAddr, EndpointAddress dstAddr) { EndpointAddress srcPeerAddress; EndpointAddress destPeer; EndpointAddress lastHop = null; boolean connectLastHop = false; EndpointAddress origSrcAddr; EndpointAddress origDstAddr; Vector origHops = null; // original route of the message EndpointRouterMessage routerMsg; EndpointAddress nextHop = null; RouteAdvertisement radv; // 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 (Logging.SHOW_FINE && LOG.isLoggable(Level.WARNING)) { LOG.warning("Discarding " + msg + ". No routing info."); } return; } try { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine(routerMsg.display()); } origSrcAddr = routerMsg.getSrcAddress(); origDstAddr = routerMsg.getDestAddress(); // convert the src and dest addresses into canonical // form stripping service info srcPeerAddress = new EndpointAddress(origSrcAddr, null, null); destPeer = new EndpointAddress(origDstAddr, null, null); lastHop = 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 (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Bad routing header or bad message. Dropping " + msg); } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Exception: ", badHdr); } return; } // Is this a loopback ? if ((srcPeerAddress != null) && srcPeerAddress.equals(localPeerAddr)) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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 (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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<AccessPointAdvertisement> reverseHops = routerMsg.getReverseHops(); if (reverseHops == null) { reverseHops = new Vector<AccessPointAdvertisement>(); } // check if we do not have a direct route // in that case we don't care to learn thelong route if (!isLocalRoute(srcPeerAddress)) { // 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) && reverseHops.firstElement().getPeerID().equals(addr2pid(lastHop))) { // Looks like a good route to learn setRoute(RouteAdvertisement.newRoute(addr2pid(srcPeerAddress), null, (Vector<AccessPointAdvertisement>) 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 peerAddress. AccessPointAdvertisement selfAp = (AccessPointAdvertisement) AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType()); selfAp.setPeerID(localPeerId); 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 (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Routing loop detected. Message dropped"); } removeRoute(addr2pid(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 = routedRoutes.get(addr2pid(srcPeerAddress)); if (newReverseRoute != null) { // we found a new route back from our cache so let's use it reverseHops = (Vector<AccessPointAdvertisement>) 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 = destPeer; } 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 (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("No next hop in forward route - Using destination as next hop"); } nextHop = destPeer; // 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) { // need to look for a long route. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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(addr2pid(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 (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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<AccessPointAdvertisement> newHops = (Vector<AccessPointAdvertisement>) 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(newHops.remove(0).getPeerID()); if (!isLocalRoute(addr)) { // Our own route is provably rotten // as well. Get rid of it. removeRoute(addr2pid(destPeer)); cantRoute("No usable route to repair the route - drop message", null, origSrcAddr, destPeer, origHops); return; } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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); routerMsg.updateMessage(); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Trying to forward to " + nextHop); } sendOnLocalRoute(nextHop, msg); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Successfully forwarded to " + nextHop); } } catch (Exception e) { cantRoute("Failed to deliver or forward message for " + destPeer, e, origSrcAddr, destPeer, origHops); } } private void cantRoute(String logMsg, Exception exception, EndpointAddress origSrcAddr, EndpointAddress destPeer, Vector origHops) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { if (exception == null) { LOG.warning(logMsg); } else { LOG.log(Level.WARNING, logMsg, exception); } } routeResolver.generateNACKRoute(addr2pid(origSrcAddr), addr2pid(destPeer), origHops); } /** * Return the address of the next hop in this vector * * @param hops 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 hard-coding * * @param p message transport * @return true if fast */ 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. * <p/> * <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 dest destination address. * @param mightWork A list of addresses to evaluate 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<EndpointAddress> mightWork, boolean exist) { List<Integer> rankings = new ArrayList<Integer>(mightWork.size()); List<EndpointAddress> worthTrying = new ArrayList<EndpointAddress>(mightWork.size()); // First rank the available addresses by type rejecting those which // cant be used. for (Object aMightWork : mightWork) { EndpointAddress addr = (EndpointAddress) aMightWork; // skip our own type if (getProtocolName().equals(addr.getProtocolName())) { continue; } int rank = -1; Iterator<MessageTransport> eachTransport = endpoint.getAllMessageTransports(); while (eachTransport.hasNext()) { MessageTransport transpt = 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(rank); worthTrying.add(addr); break; } if (rank > rankings.get(eachCurrent)) { rankings.add(eachCurrent, rank); worthTrying.add(eachCurrent, addr); break; } } } } // now that we have them ranked, go through them until we get a // successful messenger. rankings = null; for (EndpointAddress addr : worthTrying) { try { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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 // Create the listener object for that request EndpointGetMessengerAsyncListener getMessengerListener = new EndpointGetMessengerAsyncListener(this, dest); boolean stat = endpoint.getMessenger(getMessengerListener, new EndpointAddress(addr, ROUTER_SERVICE_NAME, null) , null); if (!stat) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Failed to create async messenger to : " + addr); } // 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 MAX_ASYNC_GETMESSENGER_RETRY seconds synchronized (this) { triedAndFailed.put(addr2pid(dest), TimeUtils.toAbsoluteTimeMillis(MAX_ASYNC_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 messenger = getMessengerListener.waitForMessenger(quick); if (messenger == null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("did not get our async messenger. continue"); } } else { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("we got our async messenger, proceed"); } // Success we got a messenger synchronously. Remove // the negative cache entry. synchronized (this) { triedAndFailed.remove(addr2pid(dest)); notifyAll(); } return messenger; } } catch (RuntimeException e) { // That address is somehow broken. // Cache that result for a while. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "failed checking route", e); } } } return null; } /** * Read the route advertisement for a peer and find a suitable transport * endpoint for sending to that peer either directly or via one of * the advertised peer router * * @param destPeerAddress dest address * @param exist use existing messengers, avoid creating a new one * @param hint route hint * @return a reachable messenger */ Messenger findReachableEndpoint(EndpointAddress destPeerAddress, boolean exist, RouteAdvertisement hint) { PeerID destPeerID = addr2pid(destPeerAddress); // findReachableEndpoint 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 = triedAndFailed.get(destPeerID); if (nextTry != null) { if (nextTry > 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. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Temporarly adding " + destPeerAddress.toString() + " to triedAndFailed, while attempting connection"); } triedAndFailed.put(destPeerID, TimeUtils.toAbsoluteTimeMillis(Long.MAX_VALUE)); } // Never tried or it was a long time ago. // Get (locally) the advertisements of this peer Iterator<RouteAdvertisement> advs; try { // try to use the hint that was given to us if (hint != null) { advs = Collections.singletonList(hint).iterator(); } else { // Ok extract from the CM advs = routeCM.getRouteAdv(destPeerID); } // Check if we got any advertisements List<EndpointAddress> addrs = new ArrayList<EndpointAddress>(); while (advs.hasNext()) { RouteAdvertisement adv = advs.next(); String saddr = null; // add the destination endpoint for (Enumeration<String> e = adv.getDest().getEndpointAddresses(); e.hasMoreElements();) { try { saddr = e.nextElement(); addrs.add(new EndpointAddress(saddr)); } catch (Throwable ex) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine(" bad address in route adv : " + saddr); } } } } // ok let's go and try all these addresses if (!addrs.isEmpty()) { Messenger bestMessenger = findBestReachableEndpoint(destPeerAddress, addrs, exist); if (bestMessenger != null) { // Found a direct route. Return it. // Tried+failed has been cleaned. if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("found direct route"); } return bestMessenger; } } else { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("findReachableEndpoint : Failed due to empty address list"); } } } catch (RuntimeException e) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Failure looking for an address ", e); } } // We're done trying. Since we did not find anything at all (or failed, // during the atempt) 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(destPeerID, TimeUtils.toAbsoluteTimeMillis(MAX_ASYNC_GETMESSENGER_RETRY)); } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("did not find a direct route to :" + destPeerAddress); } 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 (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("return LoopbackMessenger"); } return new LoopbackMessenger(group, 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) hint).clone(); AccessPointAdvertisement firstHop = routeHint.getFirstHop(); PeerID firstHopPid; 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) { // The hint is a direct route. Make sure that we have the // route adv so that we can actually connect. // we only need to publish this route if we don't know about // it yet. EndpointAddress da = pid2addr(routeHint.getDestPeerID()); if (!isLocalRoute(da) && !routedRoutes.containsKey(routeHint.getDestPeerID())) { routeCM.publishRoute(routeHint); } } else { // For the hint to be useful, 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(firstHop.getPeerID())) { routeFirstHop = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); routeFirstHop.setDest(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(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. Too bad // if it is not the the right one. In that mode it is the // responsibility of the application to make sure that a correct // hint was passed. return new RouterMessenger(addr, this, routeHint); } catch (IOException caught) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Can\'t generate messenger for addr " + addr, caught); } return null; } } /** * Updates the router element of a message and returns the peerAddress address of * the next hop (where to send the message). * <p/> * Currently, address message is only called for messages that we * originate. As a result we will always aggressively seek a route if needed. * * @param message the message for which to compute/update a route. * @param dstAddress 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 (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("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 (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Probable transport recursion"); } throw new IllegalStateException("RouterMessage element already present"); } routerMsg.setSrcAddress(localPeerAddr); routerMsg.setDestAddress(dstAddress); EndpointAddress theGatewayAddress; EndpointAddress dstAddressPlain = new EndpointAddress(dstAddress, null, null); try { RouteAdvertisement route = null; theGatewayAddress = getGatewayAddress(dstAddressPlain, true, null); if (theGatewayAddress == null) { // Cleanup the message, so that the invoker // may retry (with a different hint, for example). routerMsg.clearAll(); routerMsg.updateMessage(); return 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<AccessPointAdvertisement>) 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); // 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 (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "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; switch (op) { case RouteControlOp: // Get a Router Control Object return new RouteControl(this, localPeerId); default: if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Invalid Transport Control operation argument"); } return null; } } /** * Convert a Router EndpointAddress into a PeerID * * @param addr the address to extract peerAddress from * @return the PeerID */ static PeerID addr2pid(EndpointAddress addr) { URI asURI = null; try { asURI = new URI(ID.URIEncodingName, ID.URNNamespace + ":" + addr.getProtocolAddress(), null); return (PeerID) IDFactory.fromURI(asURI); } catch (URISyntaxException ex) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Error converting a source address into a virtual address : " + addr, ex); } } catch (ClassCastException cce) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Error converting a source address into a virtual address: " + addr, cce); } } return null; } /** * Convert an ID into a Router Endpoint Address * * @param pid The ID who's equivalent Endpoint Address is desired. * @return The ID as an EndpointAddress. */ static EndpointAddress pid2addr(ID pid) { return new EndpointAddress(ROUTER_PROTOCOL_NAME, pid.getUniqueValue().toString(), null, null); } /** * check if it is a new route adv * * @param route route advertisement */ void updateRouteAdv(RouteAdvertisement route) { updateRouteAdv(route, false); } /** * check if it is a new route adv * * @param route route advertisement * @param force enforce the route */ void updateRouteAdv(RouteAdvertisement route, boolean force) { try { PeerID pID = route.getDestPeerID(); // check if we updated the route if (routeCM.updateRoute(route)) { // We just dumped an adv for that dest, so we want to do a real check // on its new addresses. Remove the entry from the negative cache. synchronized (this) { Long nextTry = triedAndFailed.get(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 <= TimeUtils.toAbsoluteTimeMillis(MAX_ASYNC_GETMESSENGER_RETRY)) { triedAndFailed.remove(pID); notifyAll(); } } } } else { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Route for " + pID + " is same as existing route, not publishing it"); } if (force) { synchronized (this) { Long nextTry = triedAndFailed.get(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 <= TimeUtils.toAbsoluteTimeMillis(MAX_ASYNC_GETMESSENGER_RETRY)) { triedAndFailed.remove(pID); notifyAll(); } } } } } } catch (Exception e) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Failed to publish route advertisement", e); } } } /** * is there a pending route query for that destination * * @param peerID destination address * @return true or false */ boolean isPendingRouteQuery(PeerID peerID) { return pendingQueries.containsKey(peerID); } /** * get a pending route query info * * @param peerID destination address * @return pending route query info */ ClearPendingQuery getPendingRouteQuery(PeerID peerID) { return pendingQueries.get(peerID); } /** * Do we have a long route for that destination * * @param peerID destination address * @return true or false */ boolean isRoutedRoute(PeerID peerID) { return peerID != null && routedRoutes.containsKey(peerID); } /** * Snoop if we have a messenger * * @param addr destination address * @return Messenger */ Messenger getCachedMessenger(EndpointAddress addr) { return destinations.getCurrentMessenger(addr); } /** * Get all direct route destinations * * @return Iterator iterations of all endpoint destinations */ Iterator<EndpointAddress> getAllCachedMessengerDestinations() { return destinations.allDestinations().iterator(); } /** * Get all long route destinations * * @return Iterator iterations of all routed route destinations */ Iterator<Map.Entry<ID, RouteAdvertisement>> getRoutedRouteAllDestinations() { return routedRoutes.entrySet().iterator(); } /** * Get all long route destination addresses * * @return Iterator iterations of all routed route addresses */ Iterator<ID> getAllRoutedRouteAddresses() { return routedRoutes.keySet().iterator(); } /** * Get all pendingRouteQuery destinations * * @return All pending route query destinations */ Collection<Map.Entry<PeerID, ClearPendingQuery>> getPendingQueriesAllDestinations() { List<Map.Entry<PeerID, ClearPendingQuery>> copy = new ArrayList<Map.Entry<PeerID, ClearPendingQuery>>( pendingQueries.size()); synchronized (pendingQueries) { copy.addAll(pendingQueries.entrySet()); } return copy; } /** * Get the route CM cache Manager * * @return the route CM cache Manager */ RouteCM getRouteCM() { return routeCM; } /** * Get the route resolver manager * * @return the route resolver Manager */ RouteResolver getRouteResolver() { return routeResolver; } /** * set bad route entry * * @param addr of the bad route * @param badRoute bad route info */ 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 */ synchronized BadRoute getBadRoute(EndpointAddress addr) { return badRoutes.get(addr); } }