/* * 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 net.jxta.endpoint.router.RouteController; import net.jxta.document.AdvertisementFactory; import net.jxta.endpoint.EndpointAddress; import net.jxta.endpoint.Message; import net.jxta.endpoint.Messenger; import net.jxta.endpoint.MessengerEvent; import net.jxta.id.ID; import net.jxta.logging.Logger; import net.jxta.logging.Logging; import net.jxta.peer.PeerID; import net.jxta.protocol.AccessPointAdvertisement; import net.jxta.protocol.RouteAdvertisement; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Vector; /** * Provides an "IOCTL" style interface to the JXTA router transport */ public class RouteControl implements RouteController { private static transient final Logger LOG = Logging.getLogger(RouteControl.class.getName()); /** * Endpoint Router pointer */ private final EndpointRouter router; /** * Router CM cache */ private RouteCM routeCM; /** * local Peer Id */ private final ID localPeerId; /** * initialize RouteControl * * @param router the router * @param pid the PeerID */ public RouteControl(EndpointRouter router, ID pid) { this.router = router; this.localPeerId = pid; } /** * We can't initialize the routCM variable, because it is not created yet * in the EndpointRouter. We have to wait for init() and startApp(). */ public void start() { this.routeCM = this.router.getRouteCM(); } /** * get my local route * * @return RoutAdvertisement of the local route * @deprecated Use {@code getLocalPeerRoute()} instead. This methods will be removed * in a future release. */ @Deprecated public RouteAdvertisement getMyLocalRoute() { return router.getMyLocalRoute(); } /** * Return a route advertisement for this peer. * * @return a route advertisement for this peer. */ public RouteAdvertisement getLocalPeerRoute() { return router.getMyLocalRoute(); } /** * add a new route. For the route to be useful, we actively verify * the route by trying it * * @param newRoute route to add * @return Integer status (OK, FAILED, INVALID_ROUTE or ALREADY_EXIST) */ public int addRoute(RouteAdvertisement newRoute) { RouteAdvertisement route = newRoute.clone(); // check if the destination is not ourself if (route.getDestPeerID().equals(localPeerId)) { Logging.logCheckedDebug(LOG, "Skipping Local peer addRoute"); return ALREADY_EXIST; } AccessPointAdvertisement firstHop = route.getFirstHop(); PeerID firstHopPid; EndpointAddress firstHopAddr; // The route is not necessarily a direct route if (firstHop != null) { firstHopPid = firstHop.getPeerID(); // The first hop is ourselves. Remove it a move to the new first hop if any if (localPeerId.equals(firstHopPid)) { route.removeHop(firstHopPid); firstHop = route.getFirstHop(); } } if (firstHop == null) { // It really is a direct route. EndpointAddress destAddress = EndpointRouter.pid2addr(route.getDestPeerID()); if (router.ensureLocalRoute(destAddress, route) != null) { routeCM.publishRoute(newRoute); return OK; } if (router.isLocalRoute(destAddress) || router.isRoutedRoute(route.getDestPeerID())) { Logging.logCheckedDebug(LOG, "Skipping add Route ", destAddress, " already exists"); Logging.logCheckedDebug(LOG, "isLocalRoute() ", router.isLocalRoute(destAddress), " isRoutedRoute() : ", router.isRoutedRoute(route.getDestPeerID())); return ALREADY_EXIST; } // ok go ahead try to connect to the destination using the route info if (router.ensureLocalRoute(destAddress, route) == null) { Logging.logCheckedWarning(LOG, "Failed to connect to address :", destAddress); return FAILED; } // Use the original route for publication as we may later supply the advertisement to othe peers // which may make good use of ourselves as a first and only hop. (Normally routes are discovered // via route discovery, which automatically stiches routes to the respondant ahead of the // discovered route. But a discovered route adv is sometimes used as well). Logging.logCheckedDebug(LOG, "Publishing route :", newRoute); routeCM.publishRoute(newRoute); return OK; } // we have a long route // 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. RouteAdvertisement firstHopRoute = null; firstHopPid = firstHop.getPeerID(); firstHopAddr = EndpointRouter.pid2addr(firstHopPid); if (!router.isLocalRoute(firstHopAddr) && !router.isRoutedRoute(firstHopPid)) { firstHopRoute = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); firstHopRoute.setDest(firstHop.clone()); router.updateRouteAdv(firstHopRoute); } if (router.ensureLocalRoute(firstHopAddr, firstHopRoute) == null) { // could not find a route to the first hop, discard the route return FAILED; } router.setRoute(route.clone(), false); return OK; } /** * Get a current route info * * @param pId destination of the route * @return RouteAdvertisement current route info */ public RouteAdvertisement getRouteInfo(PeerID pId) { RouteAdvertisement route; EndpointRouter.ClearPendingQuery entry; EndpointAddress addr = EndpointRouter.pid2addr(pId); // check if we have a direct route Messenger oneOfThem = router.getCachedMessenger(addr); EndpointAddress pcaddr = (oneOfThem == null) ? null : oneOfThem.getDestinationAddress(); if (pcaddr != null) { AccessPointAdvertisement ap = (AccessPointAdvertisement) AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType()); ap.setPeerID(pId); Vector<String> eas = new Vector<String>(); eas.add(pcaddr.getProtocolName() + "://" + pcaddr.getProtocolAddress()); ap.setEndpointAddresses(eas); route = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); route.setDest(ap); return route; } else { // check if we have a long route route = router.getRoute(addr, false); if (route != null) { return route; } else { // check if we have a pending query entry = router.getPendingRouteQuery(pId); if (entry != null) { // ok we have a pending query AccessPointAdvertisement ap = (AccessPointAdvertisement) AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType()); ap.setPeerID(pId); Vector<String> eas = new Vector<String>(); eas.add("pending " + (entry.isFailed() ? "(failed)" : "(new)")); ap.setEndpointAddresses(eas); route = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); route.setDest(ap); return route; } else { // sorry no route found AccessPointAdvertisement ap = (AccessPointAdvertisement) AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType()); ap.setPeerID(pId); route = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); route.setDest(ap); return route; } } } } /** * Delete route info * * @param pId destination route to be removed * @return Integer status */ public int deleteRoute(PeerID pId) { // check if the route Id is not ourself if (pId.equals(localPeerId)) { return INVALID_ROUTE; } EndpointAddress addr = EndpointRouter.pid2addr(pId); // FIXME tra 20030820 We are only allowing to remove long routes. // Since direct routes can be used as the first hop for multiple // valid routes, we don't want to close the associate messenger. At some // point we should introduce a way to disassociate direct routes and // their corresponding messengers, so we can have a generic table of routes // and a separated table of messengers that can be manipulated // independently. // Check if we have a direct route if (router.isLocalRoute(addr)) { return DIRECT_ROUTE; } // remove routing table info router.removeRoute(pId); // flush the CM. We need to flush the CM // so the route will not be regenarated routeCM.flushRoute(pId); return OK; } /** * {@inheritDoc } */ public Collection<RouteAdvertisement> getAllRoutes() { return getAllRoutesInfo(); } /** * {@inheritDoc } */ public Collection<RouteAdvertisement> getRoutes(PeerID inPID) { // Preparing result Collection<RouteAdvertisement> Result = new ArrayList<RouteAdvertisement>(); // Checking PID if ( inPID == null ) return Result; // Preparing result Vector<RouteAdvertisement> TempRI = getAllRoutesInfo(); for (int i=0;i<TempRI.size();i++) { if (TempRI.get(i).getDestPeerID().toString().compareTo(inPID.toString())==0) { Result.add(TempRI.get(i)); } } // Returning result return Result; } /** * get all the know routes by the router. Return a vector of all * the routes known. * <p/> * This method which is meant for informational purposes, does not lock the maps that * it browses. As a result, it could in some cases generate a concurrent modification * exception. * * @return vector of known routes */ public Vector<RouteAdvertisement> getAllRoutesInfo() { Vector<RouteAdvertisement> routes = new Vector<RouteAdvertisement>(); EndpointAddress ea; try { // get the direct routes for (Iterator it = router.getAllCachedMessengerDestinations(); it.hasNext();) { ea = (EndpointAddress) it.next(); AccessPointAdvertisement ap = (AccessPointAdvertisement) AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType()); ap.setPeerID(EndpointRouter.addr2pid(ea)); Vector<String> eas = new Vector<String>(); Messenger oneOfThem = router.getCachedMessenger(ea); EndpointAddress pcaddr = (oneOfThem == null) ? null : oneOfThem.getDestinationAddress(); if (pcaddr == null) { // incomplete route eas.add("unknown"); } else { eas.add(pcaddr.getProtocolName() + "://" + pcaddr.getProtocolAddress()); } ap.setEndpointAddresses(eas); RouteAdvertisement r = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); r.setDest(ap); routes.add(r); } // now get the long routes // Use entrySet, there's no point in doing redundant lookups // in the map. for (Iterator<Map.Entry<ID, RouteAdvertisement>> i = router.getRoutedRouteAllDestinations(); i.hasNext();) { Map.Entry<ID, RouteAdvertisement> entry = i.next(); routes.add(entry.getValue()); } for (Map.Entry<PeerID, EndpointRouter.ClearPendingQuery> entry : router.getPendingQueriesAllDestinations()) { PeerID pid = entry.getKey(); AccessPointAdvertisement ap = (AccessPointAdvertisement) AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType()); ap.setPeerID(pid); Vector<String> eas = new Vector<String>(); eas.add("pending " + (entry.getValue().isFailed() ? "(failed)" : "(new)")); ap.setEndpointAddresses(eas); RouteAdvertisement r = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType()); r.setDest(ap); routes.add(r); } } catch (Exception ex) { Logging.logCheckedWarning(LOG, "getAllRoutesInfo error :\n", ex); } return routes; } /** * get RouteCM usage * * @return true if use route CM is set */ public boolean useRouteCM() { return router.getRouteCM().useRouteCM(); } /** * get RouteResolver usage * * @return true of use route resolver */ public boolean useRouteResolver() { return router.getRouteResolver().useRouteResolver(); } /** * enable usage of Route CM cache */ public void enableRouteCM() { router.getRouteCM().enableRouteCM(true); } /** * disable usage of Route CM cache */ public void disableRouteCM() { router.getRouteCM().enableRouteCM(false); } /** * enable usage of Route Resolver */ public void enableRouteResolver() { router.getRouteResolver().enableRouteResolver(true); } /** * disable usage of Route resolver */ public void disableRouteResolver() { router.getRouteResolver().enableRouteResolver(false); } /** * Get the low level messenger for a destination. * * @param source the source endpoint address * @param destination the destination endpoint address * @param messenger the messenger to add * @return true if successful */ public boolean addMessengerFor(Object source, EndpointAddress destination, Messenger messenger) { return router.newMessenger(new MessengerEvent(source, messenger, destination)); } /** * Get the low level messenger for a destination. * * @param destination the destination endpoint address * @param hint route hint * @return the messenger for the destination * @deprecated Use {@code getMessengerFor(EndpointAddress destination)} instead. * This method will be removed in a future release. * Hints should be processed via the {@code addRoute()} method. */ @Deprecated public Messenger getMessengerFor(EndpointAddress destination, Object hint) { if ((hint != null) && !(hint instanceof RouteAdvertisement)) { hint = null; } return router.ensureLocalRoute(destination, (RouteAdvertisement) hint); } /** * Get the low level messenger for a destination. * * @param destination the destination endpoint address * @return the messenger for the destination */ public Messenger getMessengerFor(EndpointAddress destination) { return router.ensureLocalRoute(destination, null); } /** * Determines whether a connection to a specific node exists, or if one can be created. * This method can block to ensure a usable connection exists, it does so by sending an empty * message. * * @param pid Node ID * @return true, if a connection already exists, or a new was sucessfully created */ public boolean isConnected(PeerID pid) { return isConnected(pid, null); } /** * @deprecated Use {@code isConnected(PeerID pid)} only. This method will be removed * in a future release. Hints should be processed via the {@code addRoute()} method. */ @Deprecated public boolean isConnected(PeerID pid, RouteAdvertisement route) { Messenger messenger = getMessengerFor(new EndpointAddress("jxta", pid.getUniqueValue().toString(), null, null), route); if (messenger == null) { return false; } if ((messenger.getState() & Messenger.USABLE) != 0) { try { //ensure it can be used messenger.sendMessageB(new Message(), null, null); } catch (IOException io) { // determine whether it is usable return (messenger.getState() & Messenger.USABLE) != 0; } } return (messenger.getState() & Messenger.CLOSED) != Messenger.CLOSED; } }