/*
* 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.credential.Credential;
import net.jxta.document.*;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.OutgoingMessageEvent;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.ID;
import net.jxta.impl.cm.Srdi;
import net.jxta.impl.cm.Srdi.SrdiInterface;
import net.jxta.impl.cm.SrdiIndex;
import net.jxta.impl.protocol.*;
import net.jxta.impl.util.TimeUtils;
import net.jxta.membership.MembershipService;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.Module;
import net.jxta.protocol.*;
import net.jxta.resolver.QueryHandler;
import net.jxta.resolver.ResolverService;
import net.jxta.resolver.SrdiHandler;
import java.util.logging.Level;
import net.jxta.logging.Logging;
import java.util.logging.Logger;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Handles dynamic route resolution.
*/
class RouteResolver implements Module, QueryHandler, SrdiHandler, SrdiInterface {
/**
* Logger
*/
private final static transient Logger LOG = Logger.getLogger(RouteResolver.class.getName());
/**
* Router Service Name
*/
public final static String routerSName = "EndpointRouter";
private final static String srdiIndexerFileName = "routerSrdi";
/**
* Negative Route query acknowledgment
*/
private final static int NACKROUTE_QUERYID = -1;
/**
* Bad route expiration. Amount of time we consider a route bad
*/
private final static long BADROUTE_EXPIRATION = 2L * TimeUtils.AMINUTE;
/**
* Default dynamic route resolution configuration preference.
*/
private final static boolean USE_ROUTE_RESOLVER_DEFAULT = true;
/**
* Configuration property that disables the usage
* of dynamic route resolution. Dynamic routes
* will not be discovered. set to true by default
* can be overwritten via ConfigParams
*/
private boolean useRouteResolver = USE_ROUTE_RESOLVER_DEFAULT;
/**
* PeerGroup Service Handle
*/
private PeerGroup group = null;
/**
* Resolver service handle
*/
private ResolverService resolver = null;
/**
* membership service
*/
private MembershipService membership = null;
/**
* EndpointRouter pointer
*/
private EndpointRouter router = null;
/**
* local peer ID as a endpointAddress.
*/
private EndpointAddress localPeerAddr = null;
/**
* local Peer ID
*/
private ID localPeerId = null;
/**
* Route CM Persistent cache
*/
private RouteCM routeCM = null;
/**
* The current resolver query ID. static to make debugging easier.
*/
private final static AtomicInteger qid = new AtomicInteger(0);
/**
* SRDI route index
*/
private SrdiIndex srdiIndex = null;
/**
* SRDI Index
*/
private Srdi srdi = null;
/**
* Encapsulates current Membership Service credential.
*/
final static class CurrentCredential {
/**
* The current default credential
*/
final Credential credential;
/**
* The current default credential in serialized XML form.
*/
final XMLDocument credentialDoc;
CurrentCredential(Credential credential, XMLDocument credentialDoc) {
this.credential = credential;
this.credentialDoc = credentialDoc;
}
}
/**
* The current Membership service default credential.
*/
CurrentCredential currentCredential;
/**
* Listener we use for membership property events.
*/
private class CredentialListener implements PropertyChangeListener {
/**
* Standard Constructor
*/
CredentialListener() {}
/**
* {@inheritDoc}
*/
public void propertyChange(PropertyChangeEvent evt) {
if (MembershipService.DEFAULT_CREDENTIAL_PROPERTY.equals(evt.getPropertyName())) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("New default credential event");
}
synchronized (RouteResolver.this) {
Credential cred = (Credential) evt.getNewValue();
XMLDocument credentialDoc;
if (null != cred) {
try {
credentialDoc = (XMLDocument) cred.getDocument(MimeMediaType.XMLUTF8);
currentCredential = new CurrentCredential(cred, credentialDoc);
} catch (Exception all) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Could not generate credential document", all);
}
currentCredential = null;
}
} else {
currentCredential = null;
}
}
}
}
}
final CredentialListener membershipCredListener = new CredentialListener();
/**
* @param router the router
*/
RouteResolver(EndpointRouter router) {
this.router = router;
}
/**
* initialize routeResolver
*/
public void init(PeerGroup group, ID assignedID, Advertisement impl) throws PeerGroupException {
ModuleImplAdvertisement implAdvertisement = (ModuleImplAdvertisement) impl;
// extract Router service configuration properties
ConfigParams confAdv = group.getConfigAdvertisement();
XMLElement paramBlock = null;
if (confAdv != null) {
paramBlock = (XMLElement) confAdv.getServiceParam(assignedID);
}
if (paramBlock != null) {
// get our tunable router parameter
Enumeration param;
param = paramBlock.getChildren("useRouteResolver");
if (param.hasMoreElements()) {
useRouteResolver = Boolean.getBoolean(((XMLElement) param.nextElement()).getTextValue());
}
}
this.group = group;
localPeerId = group.getPeerID();
localPeerAddr = EndpointRouter.pid2addr(group.getPeerID());
if (Logging.SHOW_CONFIG && LOG.isLoggable(Level.CONFIG)) {
StringBuilder configInfo = new StringBuilder("Configuring Router Transport Resolver : " + 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\tUse Route Resolver : ").append(useRouteResolver());
LOG.config(configInfo.toString());
}
}
/**
* {@inheritDoc}
*/
public int startApp(String[] arg) {
resolver = group.getResolverService();
if (null == resolver) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Endpoint Router start stalled until resolver service available");
}
return Module.START_AGAIN_STALLED;
}
membership = group.getMembershipService();
if (null == membership) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Endpoint Router start stalled until membership service available");
}
return Module.START_AGAIN_STALLED;
}
resolver.registerHandler(routerSName, this);
// create and register the srdi service
srdiIndex = new SrdiIndex(group, srdiIndexerFileName);
// Srdi is a thread but we are not going to start,
// since the service is reactive.
srdi = new Srdi(group, routerSName, this, srdiIndex);
resolver.registerSrdiHandler(routerSName, this);
synchronized (this) {
// register our credential listener.
membership.addPropertyChangeListener(MembershipService.DEFAULT_CREDENTIAL_PROPERTY, membershipCredListener);
try {
// set the initial version of the default credential.
currentCredential = null;
Credential credential = membership.getDefaultCredential();
XMLDocument credentialDoc;
if (null != credential) {
credentialDoc = (XMLDocument) credential.getDocument(MimeMediaType.XMLUTF8);
currentCredential = new CurrentCredential(credential, credentialDoc);
}
} catch (Exception all) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "could not get default credential", all);
}
}
}
// get the RouteCM cache service
routeCM = router.getRouteCM();
return Module.START_OK;
}
/**
* {@inheritDoc}
* <p/>
* Careful that stopApp() could in theory be called before startApp().
*/
public void stopApp() {
resolver.unregisterHandler(routerSName);
// unregister SRDI
resolver.unregisterSrdiHandler(routerSName);
srdiIndex.stop();
membership.removePropertyChangeListener("defaultCredential", membershipCredListener);
currentCredential = null;
resolver = null;
srdi = null;
membership = null;
}
/**
* return routeResolver usage
*
* @return routeResolver usage
*/
boolean useRouteResolver() {
return useRouteResolver;
}
/**
* enable routeResolver usage
* @param enable if true, enables route resolver
*/
void enableRouteResolver(boolean enable) {
useRouteResolver = enable;
}
/**
* issue a new route discovery resolver request
*
* @param peer the destination as a logical endpoint address
*/
protected void findRoute(EndpointAddress peer) {
RouteAdvertisement myRoute = router.getMyLocalRoute();
// No need to pursue further if we haven't initialized our own route as
// responding peers are not going to be able to respond to us.
if (myRoute == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Cannot issue a find route if we don\'t know our own route");
}
return;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Find route for peer = " + peer);
}
try {
// create a new RouteQuery message
RouteQuery doc;
doc = new RouteQuery();
doc.setDestPeerID(EndpointRouter.addr2pid(peer));
doc.setSrcRoute(myRoute);
// check if we have some bad route information
// for that peer, in that case pass the bad hop count
BadRoute badRoute;
badRoute = router.getBadRoute(peer);
if (badRoute != null) {
// ok we have a bad route
// pass the bad hops info as part of the query
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("findRoute sends query: known bad Hops" + badRoute);
}
doc.setBadHops(badRoute.getBadHops());
} else {
doc.setBadHops(null);
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Sending query for peer : " + peer);
}
XMLDocument credentialDoc;
CurrentCredential current = currentCredential;
if (null != current) {
credentialDoc = current.credentialDoc;
} else {
credentialDoc = null;
}
ResolverQuery query = new ResolverQuery();
query.setHandlerName(routerSName);
query.setCredential(credentialDoc);
query.setSrcPeer(localPeerId);
query.setQuery(doc.toString());
query.setQueryId(qid.incrementAndGet());
// only run SRDI if we are a rendezvous
// FIXME 20060106 bondolo This is not dynamic enough. The route
// resolver needs to respond to changes in rendezvous configuration
// at runtime.
if (group.isRendezvous()) {
// check where to send the query via SRDI
List<PeerID> results;
if (srdiIndex != null) {
// try to find a least 10 entries, will pick up one
// randomly. This will protect against retry. It is
// likely that a number of RDV will know about a route
results = srdiIndex.query("route", RouteAdvertisement.DEST_PID_TAG, EndpointRouter.addr2pid(peer).toString(), 10);
if (results != null && !results.isEmpty()) {
// use SRDI to send the query
// remove any non rdv peers from the candidate list
// and garbage collect the index in the process
List<PeerID> clean = cleanupAnyEdges(query.getSrcPeer(), results);
if (!clean.isEmpty()) {
// The purpose of incrementing the hopcount
// when an SRDI index match is found (we got a
// pointer to a rdv that should have the route) is to
// restrict any further forwarding. The increment
// count is only done when a matching SRDI index is
// found. Not when the replica is selected as we
// still need to forward the query. This restriction
// is purposelly done to avoid too many longjumps
// within a walk.
query.incrementHopCount();
srdi.forwardQuery(clean, query, 1);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("found an srdi entry forwarding query to SRDI peer");
}
return;
}
} else {
// it is not in our cache, look for the replica peer
// we need to send the query
PeerID destPeer = srdi.getReplicaPeer(EndpointRouter.addr2pid(peer).toString());
if (destPeer != null && !destPeer.equals(localPeerId)) {
// don't push anywhere if we do not have a replica
// or we are trying to push to ourself
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("processQuery srdiIndex DHT forward :" + destPeer);
}
srdi.forwardQuery(destPeer, query);
return;
} else {
LOG.fine("processQuery srdiIndex DHT forward resulted in no op");
}
}
}
}
// if we reach that point then we just use the resolver walk
resolver = group.getResolverService();
if (resolver != null) {
resolver.sendQuery(null, query);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("find route query sent");
}
} else {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("cannot get the resolver service");
}
}
} catch (Exception ee) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Exception in findRoute", ee);
}
}
}
/**
* {@inheritDoc}
* <p/>
* This is called by the Generic ResolverServiceImpl when processing a
* response to a query.
*/
public void processResponse(ResolverResponseMsg response) {
if (!useRouteResolver) { // Route resolver disabled
return;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("processResponse got a response");
}
// convert the response into a RouteResponse
RouteResponse doc = null;
try {
Reader ip = new StringReader(response.getResponse());
XMLDocument asDoc = (XMLDocument)
StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, ip);
doc = new RouteResponse(asDoc);
} catch (Exception e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "malformed response - discard", e);
}
return;
}
RouteAdvertisement dstRoute = doc.getDestRoute();
RouteAdvertisement srcRoute = doc.getSrcRoute();
int queryId = response.getQueryId();
EndpointAddress routingPeer = EndpointRouter.pid2addr(srcRoute.getDestPeerID());
EndpointAddress destPeer = EndpointRouter.pid2addr(dstRoute.getDestPeerID());
// check if we have a negative route response
if (queryId == NACKROUTE_QUERYID) {
AccessPointAdvertisement badHop = dstRoute.nextHop(EndpointRouter.addr2pid(routingPeer));
PeerID badPeer;
if (badHop != null) {
badPeer = badHop.getPeerID();
} else { // the bad hop is the final destination
badPeer = dstRoute.getDestPeerID();
}
processBadRoute(badPeer, dstRoute);
return;
}
// This is not our own peer adv, so we must not keep it
// for more than its expiration time.
// we only need to publish this route if
// we don't know about it yet
// XXX: here is where we could be more conservative and use isNormallyReachable() instead, thus excluding
// incoming messengers.
if ((!router.isLocalRoute(EndpointRouter.pid2addr(srcRoute.getDestPeerID())))
&& (!router.isRoutedRoute(srcRoute.getDestPeerID()))) {
router.updateRouteAdv(srcRoute);
}
if (destPeer.equals(routingPeer)) {
// The dest peer itself managed to respond to us. That means we
// learned the route from the reverseRoute in the message
// itself. So, there's nothing we need to do.
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("learn route directly from the destination");
}
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("learn route:" + routingPeer);
}
try {
// build the candidate route using the
// route response from the respondant peer
RouteAdvertisement candidateRoute = RouteAdvertisement.newRoute(EndpointRouter.addr2pid(destPeer),
EndpointRouter.addr2pid(routingPeer),(Vector) dstRoute.getVectorHops().clone());
// cleanup the candidate route from any loop and remove the local peer extra
// cycle
RouteAdvertisement.cleanupLoop(candidateRoute, (PeerID) localPeerId);
// Is there anything left in that route (or did the respondant
// believe that we are the last hop on the route - which
// obviously we are not.
if (candidateRoute.size() == 0) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Route response outdated: NACK responder");
}
generateNACKRoute(EndpointRouter.addr2pid(routingPeer), EndpointRouter.addr2pid(destPeer), dstRoute.getVectorHops());
return;
}
// get the address of the first hop in the route to verify that
// we have a route (direct or long) to the first hop, so the route
// is valid
EndpointAddress candidateRouter = EndpointRouter.pid2addr(candidateRoute.getFirstHop().getPeerID());
// check that we have a direct connection to the first hop
if (router.ensureLocalRoute(candidateRouter, null) == null) {
// If we do not have a direct route to the candidate router check
// for a long route in that case stich the route
RouteAdvertisement routeToRouter = router.getRoute(candidateRouter, false);
if (routeToRouter == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Route response useless: no route to next router hop");
}
return;
}
// stich the route removing any loops and localPeer cycle
if (RouteAdvertisement.stichRoute(candidateRoute, routeToRouter, (PeerID) localPeerId)) {
router.setRoute(candidateRoute, false);
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Route response error stiching route response");
}
return;
}
} else {
// we have a direct connection with the first hop of the candidate route
// set the new route, which starts with the peer that replied to us.
router.setRoute(candidateRoute, false);
}
} catch (Exception ex) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Failure building response route", ex);
LOG.warning(" bad dstRoute: " + dstRoute.display());
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("finish process route response successfully");
}
}
}
/**
* bad route, so let's remove everything we have so
* we can start from scratch. We are maintaining a
* bad route up to DEFAULT_ROUTE expiration after
* that we consider it to be ok to retry the same route
* We are removing both the route and peer advertisement
* to force a new route query
*
* @param badHop source PeerID of NACK route info
* @param dest original route information
*/
private void processBadRoute(PeerID badHop, RouteAdvertisement dest) {
EndpointAddress addr = EndpointRouter.pid2addr(dest.getDestPeerID());
if (addr == null) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("remove bad route has a bad route info - discard");
}
return;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("remove bad route info for dest " + dest.display());
if (badHop != null) {
LOG.fine("remove bad route bad hop " + badHop);
}
}
try {
// check first that we still have the same route, we may already
// using a new route
RouteAdvertisement currentRoute = router.getRoute(addr, false);
if (currentRoute == null) { // we already cleanup the route info
return;
}
// check if we still have the old bad route, we may have
// already updated the route
if (!currentRoute.equals(dest)) {
// check if the bad hop is not the destination
// if it is then we still have a bad route
if (badHop == null) {
// we could get the bad hop, so consider the route ok
return;
}
if (badHop.equals(EndpointRouter.addr2pid(addr))) {
// check if the new route may still contain the bad hop
// the known bad hop is the hop after the src peer that
// responded with a NACK route
// In this case we also consider the route bad
if (!currentRoute.containsHop(badHop)) {
return; // we are ok
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("current route is bad because it contains known bad hop" + badHop);
}
}
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("current route is bad because it contains known bad destination" + badHop);
}
}
}
// keep the bad one in a cache table so we don't retry them
// right away. We use the default route timeout
BadRoute badRoute = (router.getBadRoute(addr));
if (badRoute != null) {
if (badRoute.getExpiration() > TimeUtils.timeNow()) {// nothing to do. the information is still valid
} else {
// It is ancient knowlege update it
badRoute.setExpiration(TimeUtils.toAbsoluteTimeMillis(BADROUTE_EXPIRATION));
}
// check if we have to add a new bad hop
// to our bad route
if (badHop != null) {
badRoute.addBadHop(badHop);
badRoute.setExpiration(TimeUtils.toAbsoluteTimeMillis(BADROUTE_EXPIRATION));
}
router.setBadRoute(addr, badRoute);
return;
} else {
// create a new NACK route entry
Set<PeerID> badHops;
if (badHop != null) {
badHops = Collections.singleton(badHop);
} else {
badHops = Collections.emptySet();
}
badRoute = new BadRoute(dest, TimeUtils.toAbsoluteTimeMillis(BADROUTE_EXPIRATION), badHops);
router.setBadRoute(addr, badRoute);
}
// remove route from route CM
routeCM.flushRoute(EndpointRouter.addr2pid(addr));
// let's remove the remote route info from the routing table
// we do this after we removed the entries from the CM
// to avoid that another thread is putting back the entry
router.removeRoute(EndpointRouter.addr2pid(addr));
} catch (Exception ex) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "exception during bad route removal", ex);
}
}
}
/**
* Process the Query, and generate response
*
* @param query the query to process
*/
public int processQuery(ResolverQueryMsg query) {
if (!useRouteResolver) { // Route resolver disabled
return ResolverService.OK;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("processQuery starts");
}
RouteQuery routeQuery;
try {
Reader ip = new StringReader(query.getQuery());
XMLDocument asDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, ip);
routeQuery = new RouteQuery(asDoc);
} catch (RuntimeException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Malformed Route query ", e);
}
return ResolverService.OK;
} catch (IOException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Malformed Route query ", e);
}
return ResolverService.OK;
}
PeerID pId = routeQuery.getDestPeerID();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Looking for route to " + pId);
}
RouteAdvertisement srcRoute = routeQuery.getSrcRoute();
Collection<PeerID> badHops = routeQuery.getBadHops();
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
StringBuilder badHopsDump = new StringBuilder("bad Hops :\n");
for (ID aBadHop : badHops) {
badHopsDump.append('\t').append(aBadHop);
}
LOG.finer(badHopsDump.toString());
}
// if our source route is not null, then publish it
if (srcRoute != null) {
if (!(srcRoute.getDestPeerID()).equals(localPeerId)) {
// This is not our own peer adv so we must not keep it
// longer than its expiration time.
try {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Publishing sender route info " + srcRoute.getDestPeerID());
}
// we only need to publish this route if
// we don't know about it yet
// XXX: here is where we could be more conservative and use isNormallyReachable() instead, thus excluding
// incoming messengers.
if ((!router.isLocalRoute(EndpointRouter.pid2addr(srcRoute.getDestPeerID())))
&& (!router.isRoutedRoute(srcRoute.getDestPeerID()))) {
routeCM.publishRoute(srcRoute);
}
} catch (Exception e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Could not publish Route Adv from query - discard", e);
}
return ResolverService.OK;
}
}
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("No src Route in route query - discard ");
}
return ResolverService.OK;
}
if (pId == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Malformed route query request, no PeerId - discard");
}
return ResolverService.OK;
}
// We have more luck with that one because, since it is part of OUR
// message, and not part of the resolver protocol, it is in OUR
// format.
EndpointAddress qReqAddr = EndpointRouter.pid2addr(pId);
RouteAdvertisement route;
// check if this peer has a route to the destination
// requested
boolean found = false;
if (qReqAddr.equals(localPeerAddr)) {
found = true;
// return the route that is my local route
route = router.getMyLocalRoute();
} else {
// only rendezvous can respond to route requests
// if not we are generating too much traffic
// XXX: here is where we could be more conservative and use isNormallyReachable() instead, thus excluding
// incoming messengers.
if (router.isLocalRoute(qReqAddr)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Peer has direct route to destination ");
}
// we should set the route to something :-)
found = true;
// this peer has a direct route to the destination
// return the short route advertisement we know for this peer
// (For us it is zero hop, and we advertise ourself as the routing
// peer in the response. The stiching is done by whoever gets that
// response). May be there are more than one hop advertised in-there...
// alternate routing peers...should we leave them ?
// For now, we keep the full dest, but wack the hops.
route = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType());
AccessPointAdvertisement ap = (AccessPointAdvertisement)
AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
ap.setPeerID(pId);
route.setDest(ap);
} else {
route = router.getRoute(qReqAddr, false);
if (route != null) {
found = true;
// check if we were given some bad hops info
// and see if the found route contains
// any of these bad hops. In that case, we need
// to mark this route as bad
for (PeerID aBadHop : badHops) {
// destination is known to be bad
if (EndpointRouter.addr2pid(qReqAddr).equals(aBadHop)) {
processBadRoute(aBadHop, route);
found = false;
break;
}
if (route.containsHop(aBadHop)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Peer has bad route due to " + aBadHop);
}
processBadRoute(aBadHop, route);
found = false;
break;
}
}
}
}
}
if (!found) {
// discard the request if we are not a rendezvous
// else forward to the next peers
if (!group.isRendezvous()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("discard query forwarding as not a rendezvous");
}
return ResolverService.OK;
}
// did not find a route, check our srdi cache
// make sure we protect against out of sync
// SRDI index
// srdi forwarding is only involved once the Index entry has
// been found and we forwarded the resolver query. Afterward a
// normal walk proceeds from the initial SRDI index pointing
// rdv. This is done to protect against potential loopback
// entries in the SRDI cache index due to out of sync peerview
// and index.
if (query.getHopCount() < 2) {
// check local SRDI cache to see if we have the entry
// we look for 10 entries, will pickup one randomly
List<PeerID> results = srdiIndex.query("route", RouteAdvertisement.DEST_PID_TAG, pId.toString(), 10);
if (results.size() > 0) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("processQuery srdiIndex lookup match :" + results.size());
}
// remove any non-rdv peers to avoid sending
// to a non-rdv peers and garbage collect the SRDI
// index in the process
List<PeerID> clean = cleanupAnyEdges(query.getSrcPeer(), results);
if (clean.size() > 0) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("found an srdi entry forwarding query to SRDI peer");
}
// The purpose of incrementing the hopcount
// when an SRDI index match is found (we got a
// pointer to a rdv that should have the route) is to
// restrict any further forwarding. The increment
// count is only done when a matching SRDI index is
// found. Not when the replica is selected as we
// still need to forward the query. This restriction
// is purposelly done to avoid too many longjumps
// within a walk.
query.incrementHopCount();
// Note: this forwards the query to 1 peer randomly
// selected from the result
srdi.forwardQuery(clean, query, 1);
// tell the resolver no further action is needed.
return ResolverService.OK;
}
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("did not find a route or SRDI index");
}
// force a walk
return ResolverService.Repropagate;
}
// we found a route send the response
try {
if (route == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("we should have had a route at this point");
}
return ResolverService.OK;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("we have a route build route response" + route.display());
}
RouteAdvertisement myRoute = router.getMyLocalRoute();
// make sure we initialized our local
// route info as we will need it to respond. We may
// not have our route if we are still
// waiting for a relay connection.
if (myRoute == null) {
return ResolverService.OK;
}
RouteResponse routeResponse = new RouteResponse();
routeResponse.setDestRoute(route);
routeResponse.setSrcRoute(myRoute);
if (routeResponse == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("error creating route response");
}
return ResolverService.OK;
}
// construct a response from the query
ResolverResponseMsg res = query.makeResponse();
CurrentCredential current = currentCredential;
if (null != current) {
res.setCredential(current.credentialDoc);
}
res.setResponse(routeResponse.toString());
resolver.sendResponse(query.getSrcPeer().toString(), res);
return ResolverService.OK;
} catch (Exception ee) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "processQuery: error while processing query ", ee);
}
return ResolverService.OK;
}
}
/**
* Return a route error in case a route was found to be invalid
* as the current hop cannot find a way to forward the message to the
* destination or any other hops in the forward part of the route.
* In that case a negative route response is forwarded
* to the original source of the message. Now of course we
* do not have any way to guarantee that the NACK message will be
* received by the sender, but the best we can do is to try :-)
* <p/>
* we send a query ID to NACKROUTE_QUERYID to indicate
* this is a bad Route
*
* @param src original source of the message
* @param dest original destination of the message
* @param origHops original hops
*/
protected void generateNACKRoute(PeerID src, PeerID dest, Vector<AccessPointAdvertisement> origHops) {
// As long as the group is partially initialized, do not bother
// trying to send NACKS. We can't: it just causes NPEs.
if (resolver == null) {
return;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("generate NACK Route response " + src);
}
// check first, if we are not already in process of looking for a
// route to the destination peer of the NACK. We should not try to
// send a NACK to that destination at that point as this will block
// our incoming processing thread while it is looking for a route to
// that destination. If there a no pending route requests to that
// destination then we can go ahead an attempt to send the NACK. At
// the maximum we should have only one thread block while looking for
// a specific destination. When we find a route to the destination,
// the next NACK processing will be sent.
if (router.isPendingRouteQuery(src)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("drop NACK due to pending route discovery " + src);
}
return;
}
// Generate a route response
RouteAdvertisement route = (RouteAdvertisement)
AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType());
AccessPointAdvertisement ap = (AccessPointAdvertisement)
AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
ap.setPeerID(dest);
route.setDest(ap);
route.setHops(origHops);
// set the the route of the peer that
// detected the bad route
RouteAdvertisement routeSrc = (RouteAdvertisement)
AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType());
AccessPointAdvertisement apSrc = (AccessPointAdvertisement)
AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
apSrc.setPeerID((PeerID) localPeerId);
routeSrc.setDest(apSrc);
RouteResponse routeResponse = new RouteResponse();
routeResponse.setDestRoute(route);
routeResponse.setSrcRoute(routeSrc);
ResolverResponse res = new ResolverResponse();
res.setHandlerName(routerSName);
CurrentCredential current = currentCredential;
if (null != current) {
res.setCredential(current.credentialDoc);
}
res.setQueryId(NACKROUTE_QUERYID);
res.setResponse(routeResponse.toString());
// send the NACK response back to the originator
resolver.sendResponse(src.toString(), res);
}
/**
* process an SRDI message request
*
* @param message SRDI resolver message
*/
public boolean processSrdi(ResolverSrdiMsg message) {
if(!group.isRendezvous()) {
return true;
}
String value;
SrdiMessage srdiMsg;
try {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Received a SRDI messsage in group" + group.getPeerGroupName());
}
XMLDocument asDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, new StringReader(message.getPayload()));
srdiMsg = new SrdiMessageImpl(asDoc);
} catch (Exception e) {
// we don't understand this msg, let's skip it
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "corrupted SRDI message", e);
}
return false;
}
PeerID pid = srdiMsg.getPeerID();
// filter messages that contain messages
// about the local peer, so we don't enter
// self-reference
if (pid.equals(localPeerId)) {
return false;
}
for (SrdiMessage.Entry entry : srdiMsg.getEntries()) {
// drop any information about ourself
if (entry.key.equals(localPeerId.toString())) {
continue;
}
value = entry.value;
if (value == null) {
value = "";
}
// Expiration of entries is taken care of by SrdiIdex, so we always add
// FIXME hamada 20030314
// All routes are added under the secondary key 'DstPID', it would be more correct to
// Specify it in the message, but since versioning is not yet supported the following is
// acceptable, since it is localized
srdiIndex.add(srdiMsg.getPrimaryKey(), RouteAdvertisement.DEST_PID_TAG, entry.key, pid, entry.expiration);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Primary Key [" + srdiMsg.getPrimaryKey() + "] key [RouteAdvertisement.DEST_PID_TAG]" + " value [" + entry.key + "] exp [" + entry.expiration + "]");
}
}
return true;
}
/**
* {@inheritDoc}
*/
public void pushEntries(boolean all) {
// only send to the replica
pushSrdi(null, all);
}
/*
* push all srdi entries to the rednezvous SRDI cache (new connection)
*
*@param all if true push all entries, otherwise just deltas
*/
protected void pushSrdi(String peer, boolean all) {
SrdiMessage srdiMsg;
Vector<SrdiMessage.Entry> routeIx = new Vector<SrdiMessage.Entry>();
// 20021018 tra:Route info don't expire unless the peer disappears
// This approach is used to limit the SRDI traffic. The key
// point here is that SRDI is used to tell a peer that another
// has a route to the destination it is looking for. The information
// that SRDI cache is not so much the specific route info but rather
// the fact that a peer has knowledge of a route to another peer
// We don't want to update the SRDI cache on every route update.
// The SRDI cache will be flushed when the peer disconnect from
// the rendezvous.
// We cannot support concurrent modification of the map while we
// do that: we must synchronize...
for (Iterator<ID> each = router.getAllRoutedRouteAddresses(); each.hasNext();) {
ID pid = each.next();
SrdiMessage.Entry entry = new SrdiMessage.Entry(pid.toString(), "", Long.MAX_VALUE);
routeIx.addElement(entry);
}
try {
// check if we have anything to send
if (routeIx.size() == 0) {
return;
}
srdiMsg = new SrdiMessageImpl(group.getPeerID(),
// one hop
1,
"route", routeIx);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Sending a SRDI messsage of [All=" + all + "] routes");
}
// this will replicate entry to the SRDI replica peers
srdi.replicateEntries(srdiMsg);
} catch (Exception e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "SRDI Push failed", e);
}
}
}
/*
* push srdi entries to the SRDI rendezvous cache
* @param all if true push all entries, otherwise just deltas
*/
protected void pushSrdi(ID peer, PeerID id) {
SrdiMessage srdiMsg;
try {
srdiMsg = new SrdiMessageImpl(group.getPeerID(), 1, // only one hop
"route", id.toString(), null, Long.MAX_VALUE); // maximum expiration
// 20021018 tra:Route info don't expire unless the peer disappears
// This approach is used to limit the SRDI traffic. The key
// point here is that SRDI is used to tell a peer that another
// has a route to the destination it is looking for. The information
// that SRDI cache is not so much the specific route info but rather
// the fact that a peer has knowledge of a route to another peer
// We don't want to update the SRDI cache on every route update.
// The SRDI cache will be flushed when the peer disconnect from
// the rendezvous.
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("sending a router SRDI message add route " + id);
}
if (peer == null) {
PeerID destPeer = srdi.getReplicaPeer(id.toString());
peer = destPeer;
}
// don't push anywhere if we do not have a replica
// or we are trying to send the query to ourself
if (!localPeerId.equals(peer)) {
srdi.pushSrdi(peer, srdiMsg);
}
} catch (Exception e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "SRDI push failed", e);
}
}
}
/**
* remove a SRDI cache entry
*
* @param peer peer id we send the request, null for sending to all
* @param id peer id of the SRDI route that we want to remove
* from the cache
*/
protected void removeSrdi(String peer, PeerID id) {
SrdiMessage srdiMsg;
try {
srdiMsg = new SrdiMessageImpl(group.getPeerID(), 1, // only one hop
"route", id.toString(), null, // 0 means remove
0);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("sending a router SRDI message delete route " + id);
}
if (peer == null) {
PeerID destPeer = srdi.getReplicaPeer(id.toString());
// don't push anywhere if we do not have replica
// or we are trying to push to ouself
if (destPeer != null && (!destPeer.equals(localPeerId))) {
srdi.pushSrdi(destPeer, srdiMsg);
}
}
} catch (Exception e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Removing srdi entry failed", e);
}
}
}
/**
* {@inheritDoc}
*/
public void messageSendFailed(PeerID peerid, OutgoingMessageEvent e) {
// when the resolver failed to send, we get a notification and
// flush the SRDI cache entries for that destination
removeSrdiIndex(peerid);
}
/**
* cleanup any edge peers when trying to forward an SRDI query so we are
* guaranteed to the best of our knowledge that the peer is a rendezvous.
* This is not perfect, as it may take time for the peerview to converge but
* at least we can remove any peers that is not a rendezvous.
*
* @param src source
* @param results vector of PeerIDs
* @return cleaned up vector of PeerIDs
*/
protected List<PeerID> cleanupAnyEdges(ID src, List<PeerID> results) {
List<PeerID> clean = new ArrayList<PeerID>(results.size());
// put the peerview as a vector of PIDs
List<PeerID> rpvId = srdi.getGlobalPeerView();
// remove any peers not in the current peerview
// these peers may be gone or have become edges
for (PeerID pid : results) {
// eliminate the src of the query so we don't resend
// the query to whom send it to us
if (src.equals(pid)) {
continue;
}
// remove the local also, so we don't send to ourself
if (localPeerId.equals(pid)) {
continue;
}
if (rpvId.contains(pid)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("valid rdv for SRDI forward " + pid);
}
clean.add(pid);
} else {
// cleanup our SRDI cache for that peer
srdiIndex.remove(pid);
}
}
return clean;
}
/**
* remove SRDI index
*
* @param pid of the index to be removed
*/
protected void removeSrdiIndex(PeerID pid) {
srdiIndex.remove(pid);
}
}