/*
* 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.
*/
/**
* This class is used to control the Router route options
*
*/
package net.jxta.impl.endpoint.router;
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.Logging;
import net.jxta.peer.PeerID;
import net.jxta.protocol.AccessPointAdvertisement;
import net.jxta.protocol.RouteAdvertisement;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Provides an "IOCTL" style interface to the JXTA router transport
*/
public class RouteControl {
/**
* Logger
*/
private static transient final Logger LOG = Logger.getLogger(RouteControl.class.getName());
/**
* return value for operation
*/
public final static int OK = 0; // operation succeeded
public final static int ALREADY_EXIST = 1; // failed route already exists
public final static int FAILED = -1; // failed operation
public final static int DIRECT_ROUTE = 2; // failed direct route
public final static int INVALID_ROUTE = 3; // invalid route
/**
* Endpoint Router pointer
*/
private final EndpointRouter router;
/**
* Router CM cache
*/
private final 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.routeCM = router.getRouteCM();
this.localPeerId = pid;
}
/**
* get my local route
*
* @return RoutAdvertisement of the local route
*/
public RouteAdvertisement getMyLocalRoute() {
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)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("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())) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Skipping add Route " + destAddress + " already exists");
LOG.fine("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) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("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).
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("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;
}
/**
* 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) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "getAllRoutesInfo error : ", 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
*/
public Messenger getMessengerFor(EndpointAddress destination, Object hint) {
if ((hint != null) && !(hint instanceof RouteAdvertisement)) {
hint = null;
}
return router.ensureLocalRoute(destination, (RouteAdvertisement) hint);
}
/**
* 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);
}
/**
* 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
* @param route Destination route advertisement
* @return true, if a connection already exists, or a new was sucessfully created
*/
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;
}
}