/*
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.protocol;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.ExtendableAdvertisement;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.id.ID;
import net.jxta.id.IDFactory;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroupID;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.Vector;
/**
* Advertisement used to represent a route to a peer. Routes are represented in
* a generic manner as a sequence of hops to reach the destination. Each hop
* represent a potential relay peer in the route:
* <p/>
* <pre> Dest
* hop 1
* hop 2
* hop 3
* ....
* hop n
* </pre>
* <p/>
* A route can have as many hops as necessary. Hops are implicitly ordered
* starting from the hop with the shortest route to reach the destination. If a
* peer cannot reach directly the dest, it should try to reach in descending
* order one of the listed hops. Some hops may have the same physical distance
* to the destination. Some hops may define alternative routes.
* <p/>
* The destination and hops are defined using a AccessPointAdvertisements.
*
* @see net.jxta.protocol.PeerAdvertisement
* @see net.jxta.protocol.AccessPointAdvertisement
*/
public abstract class RouteAdvertisement extends ExtendableAdvertisement implements Cloneable {
public static final String DEST_PID_TAG = "DstPID";
/**
* AccessPointAdvertisement of destination peer.
*/
private transient AccessPointAdvertisement dest = (AccessPointAdvertisement)
AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
/**
* Semi-ordered list of alternative hops to the destination.
*/
private transient Vector<AccessPointAdvertisement> hops = new Vector<AccessPointAdvertisement>();
/**
* Cached value for {@link #getID()}
*/
private transient ID hashID = null;
/**
* construct a new route
* <p/>
* <b>WARNING hops may be MODIFIED.</b>
*
* @param destPid destination
* @param firsthop first hop node ID
* @param hops routes
* @return the new route
*/
public static RouteAdvertisement newRoute(PeerID destPid, PeerID firsthop, Vector<AccessPointAdvertisement> hops) {
if (destPid == null) {
throw new IllegalArgumentException("Missing destination peer id.");
}
for (AccessPointAdvertisement apa : hops) {
if (null == apa) {
throw new IllegalArgumentException("Bad route. null APA.");
}
if (apa.getPeerID() == null) {
throw new IllegalArgumentException("Bad route. Incomplete APA.");
}
}
RouteAdvertisement route = (RouteAdvertisement)
AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType());
route.setDestPeerID(destPid);
// set the route hops
route.setHops(hops);
// check if the given first hop is already in the route if not add it
// (note: we do not expect it to be there, but it is acceptable).
if (firsthop != null) {
AccessPointAdvertisement ap = route.getFirstHop();
if (ap == null || !ap.getPeerID().equals(firsthop)) {
ap = (AccessPointAdvertisement)
AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
ap.setPeerID(firsthop);
route.setFirstHop(ap);
}
}
return route;
}
/**
* {@inheritDoc}
*/
@Override
public RouteAdvertisement clone() {
try {
RouteAdvertisement a = (RouteAdvertisement) super.clone();
a.setDest(getDest());
// deep copy of the hops
Vector<AccessPointAdvertisement> clonehops = getVectorHops();
ListIterator<AccessPointAdvertisement> eachHop = clonehops.listIterator();
while (eachHop.hasNext()) {
eachHop.set(eachHop.next().clone());
}
a.setHops(clonehops);
return a;
} catch (CloneNotSupportedException impossible) {
throw new Error("Object.clone() threw CloneNotSupportedException", impossible);
}
}
/**
* makes a copy of a route advertisement
* that only contains PID not endpoint addresses
*
* @return object clone route advertisement
*/
public RouteAdvertisement cloneOnlyPIDs() {
RouteAdvertisement routeAdvertisement;
try {
routeAdvertisement = (RouteAdvertisement) super.clone();
//make sure endpoint addresses are removed;
routeAdvertisement.removeDestEndpointAddresses(routeAdvertisement.getDestEndpointAddresses());
} catch (CloneNotSupportedException impossible) {
throw new Error("Object.clone() threw CloneNotSupportedException", impossible);
}
// deep copy of the hops
Vector<AccessPointAdvertisement> clonehops = routeAdvertisement.getVectorHops();
ListIterator<AccessPointAdvertisement> eachHop = clonehops.listIterator();
while (eachHop.hasNext()) {
AccessPointAdvertisement aHop = eachHop.next();
eachHop.set(aHop.clone());
}
routeAdvertisement.setHops(clonehops);
return routeAdvertisement;
}
/**
* Compare if two routes are equals. Equals means same destination with the
* same endpoint addresses and thee same number of hops and the same
* endpoint addresses for each hop.
*
* @param target the route to compare against
* @return boolean true if the route is equal to this route otherwise false
*/
@Override
public boolean equals(Object target) {
if (this == target) {
return true;
}
if (!(target instanceof RouteAdvertisement)) {
return false;
}
RouteAdvertisement route = (RouteAdvertisement) target;
// check the destination
if (!dest.equals(route.getDest())) {
return false;
}
// check each of the hops
// routes need to have the same size
if (hops.size() != route.size()) {
return false;
}
int index = 0;
for (AccessPointAdvertisement hop : route.hops) {
if (!hop.equals(hops.get(index++))) {
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
if (null != dest.getPeerID()) {
return dest.getPeerID().hashCode();
} else {
// force all incomplete advertisements to hash to the same place.
return 1;
}
}
/**
* Returns the identifying type of this Advertisement.
*
* @return String the type of advertisement
*/
public static String getAdvertisementType() {
return "jxta:RA";
}
/**
* {@inheritDoc}
*/
@Override
public final String getBaseAdvType() {
return getAdvertisementType();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized ID getID() {
if (null == dest.getPeerID()) {
throw new IllegalStateException("Destination peerID not defined. Incomplete RouteAdvertisement");
}
if (hashID == null) {
try {
// We have not yet built it. Do it now
byte[] seed = getAdvertisementType().getBytes("UTF-8");
InputStream in = new ByteArrayInputStream(dest.getPeerID().toString().getBytes("UTF-8"));
hashID = IDFactory.newCodatID((PeerGroupID) dest.getPeerID().getPeerGroupID(), seed, in);
} catch (Exception ez) {
return ID.nullID;
}
}
return hashID;
}
/**
* Returns the route destination Peer ID
*
* @return peerID of the destination of the route
*/
public PeerID getDestPeerID() {
return dest.getPeerID();
}
/**
* Sets the route destination peer id.
*
* @param pid route destination peerID
*/
public void setDestPeerID(PeerID pid) {
if ((null != pid) && (null != dest.getPeerID()) && (!pid.equals(dest.getPeerID()))) {
throw new IllegalStateException("Changing the peer id of the destination APA." + pid + " != " + dest.getPeerID());
}
dest.setPeerID(pid);
// recalculate hash.
synchronized (this) {
hashID = null;
}
}
/**
* Returns the destination access point. <b>This is a clone of
* the AccessPointAdvertisement</b>.
*
* @return AccessPointAdvertisement of the destination peer.
*/
public AccessPointAdvertisement getDest() {
return dest.clone();
}
/**
* Sets the access point of the destination. <b>This does <i>NOT</i> copy
* the AccessPointAdvertisement</b>.
*
* @param ap AccessPointAdvertisement of the destination peer
*/
public void setDest(AccessPointAdvertisement ap) {
PeerID destPid = dest.getPeerID();
this.dest = ap.clone();
if ((null != destPid) && (null != dest.getPeerID()) && (!destPid.equals(dest.getPeerID()))) {
throw new IllegalStateException("Changed the peer id of the destination APA." + destPid + " != " + dest.getPeerID());
}
if (null != destPid) {
dest.setPeerID(destPid);
}
// recalculate hash.
synchronized (this) {
hashID = null;
}
}
/**
* Clears all endpoint addresses associated with the destination peer.
*/
public void clearDestEndpointAddresses() {
dest.clearEndpointAddresses();
}
/**
* Add the specified endpoint address to destination peer.
*
* @param addr EndpointAddress to add.
*/
public void addDestEndpointAddress(EndpointAddress addr) {
dest.addEndpointAddress(addr);
}
/**
* Add all of the specified endpoint addresses to destination peer.
*
* @param addrs EndpointAddresses to add.
*/
public void addDestEndpointAddresses(List<EndpointAddress> addrs) {
dest.addEndpointAddresses(addrs);
}
/**
* Remove the specified endpoint address to destination peer.
*
* @param addr EndpointAddress to add.
*/
public void removeDestEndpointAddress(EndpointAddress addr) {
dest.removeEndpointAddress(addr);
}
/**
* Remove the specified endpoint addresses from destination peer.
*
* @param addrs EndpointAddress to add.
*/
public void removeDestEndpointAddresses(Collection<EndpointAddress> addrs) {
dest.removeEndpointAddresses(addrs);
}
/**
* Returns the endpoint addresses of the destination peer in their
* preferred order.
*
* @return The {@code EndpointAddress}es of the destination peer.
*/
public List<EndpointAddress> getDestEndpointAddresses() {
List<EndpointAddress> result = new ArrayList<EndpointAddress>();
Enumeration<String> eachEA = dest.getEndpointAddresses();
while (eachEA.hasMoreElements()) {
result.add(new EndpointAddress(eachEA.nextElement()));
}
return result;
}
/**
* returns the list of hops
*
* @return Enumeration list of hops as AccessPointAdvertisement
*/
public Enumeration<AccessPointAdvertisement> getHops() {
return Collections.enumeration(hops);
}
/**
* returns the list of hops
*
* @return Vector list of hops as AccessPointAdvertisement
*/
public Vector<AccessPointAdvertisement> getVectorHops() {
return hops;
}
/**
* Sets the list of hops associated with this route.
*
* @param newHops AccessPointAdvertisements which form the hops. The
* Vector is <b>NOT</b> copied.
*/
public void setHops(Vector<AccessPointAdvertisement> newHops) {
// It is legal to set it to null but it is automatically converted
// to an empty vector. The member hops is NEVER null.
if (null == newHops) {
hops = new Vector<AccessPointAdvertisement>();
} else {
for (AccessPointAdvertisement hop : newHops) {
if (null == hop.getPeerID()) {
throw new IllegalArgumentException("Bad hop");
}
}
hops = newHops;
}
}
/**
* Check if the route contains the following hop
*
* @param pid peer id of the hop
* @return boolean true or false if the hop is found in the route
*/
public boolean containsHop(PeerID pid) {
for (AccessPointAdvertisement hop : hops) {
PeerID hid = hop.getPeerID();
if (pid.equals(hid)) {
return true;
}
}
return false;
}
/**
* Returns the AccessPointAdvertisement of first hop. <b>The
* AccessPointAdvertisement is <i>not</i> cloned.</b>
*
* @return AccessPointAdvertisement of first hop.
*/
public AccessPointAdvertisement getFirstHop() {
return hops.isEmpty() ? null : hops.get(0);
}
/**
* Sets the AccessPointAdvertisement for the first hop. <b>The
* AccessPointAdvertisement is <i>not</i> cloned.</b>
*
* @param ap AccessPointAdvertisement of the first hop.
*/
public void setFirstHop(AccessPointAdvertisement ap) {
if (null == ap.getPeerID()) {
throw new IllegalArgumentException("Bad hop");
}
hops.add(0, ap);
}
/**
* Returns the access point for the last hop. <b>The
* AccessPointAdvertisement is <i>not</i> cloned.</b>
*
* @return AccessPointAdvertisement last hop.
*/
public AccessPointAdvertisement getLastHop() {
return hops.isEmpty() ? null : hops.lastElement();
}
/**
* Sets the AccessPointAdvertisement of the last hop. <b>The
* AccessPointAdvertisement is <i>not</i> cloned.</b>
*
* @param ap AccessPointAdvertisement of the last hop.
*/
public void setLastHop(AccessPointAdvertisement ap) {
if (null == ap.getPeerID()) {
throw new IllegalArgumentException("Bad hop");
}
hops.add(ap);
}
/**
* check if the route has a loop
*
* @return boolean true or false if the route has a loop
*/
public boolean hasALoop() {
// Now check for any other potential loops.
Set<PeerID> seenPeers = new HashSet<PeerID>(hops.size());
for (AccessPointAdvertisement anAPA : hops) {
PeerID pid = anAPA.getPeerID();
if (seenPeers.contains(pid)) {
return true; // There is a loop.
}
seenPeers.add(pid);
}
return false;
}
/**
* return the length of the route
*
* @return int size of the route
*/
public int size() {
return hops.size();
}
/**
* Return the hop that follows the specified currentHop. <b>The
* AccessPointAdvertisement is <i>not</i> cloned.</b>
*
* @param currentHop PeerID of the current hop
* @return ap AccessPointAdvertisement of the next Hop
*/
public AccessPointAdvertisement nextHop(PeerID currentHop) {
// check if we have a real route
if (hops.isEmpty()) {
// Empty vector.
return null;
}
// find the index of the route
int index = 0;
boolean found = false;
for (AccessPointAdvertisement ap : hops) {
if (currentHop.equals(ap.getPeerID())) {
found = true;
break;
}
index++;
}
AccessPointAdvertisement nextHop = null;
if (!found) {
// The peer is not into the list. Since we have got that message,
// the best we can do is to send it to the first gateway in the
// forward path.
nextHop = hops.get(0);
} else {
// Found the peer within the vector of hops. Get the next hop.
if (index < hops.size()) {
nextHop = hops.get(index);
}
}
return nextHop;
}
/**
* Generate a string that displays the route
* information for logging or debugging purpose
*
* @return String return a string containing the route info
*/
public String display() {
StringBuilder routeBuf = new StringBuilder();
routeBuf.append("Dest APA : ");
AccessPointAdvertisement dest = getDest();
routeBuf.append(dest.display());
routeBuf.append("\n");
int i = 1;
Enumeration<AccessPointAdvertisement> e = getHops();
while (e.hasMoreElements()) {
AccessPointAdvertisement hop = e.nextElement();
if (i == 1) {
routeBuf.append("HOPS = ");
}
routeBuf.append("\n\t[").append(i++).append("] ");
routeBuf.append(hop.display());
}
return routeBuf.toString();
}
/**
* Remove a hop from the list of hops.
*
* @param pid peer id of the hop
* @return boolean true or false if the hop is found in the route
*/
public boolean removeHop(PeerID pid) {
Iterator<AccessPointAdvertisement> eachHop = hops.iterator();
while (eachHop.hasNext()) {
AccessPointAdvertisement hop = eachHop.next();
PeerID hid = hop.getPeerID();
if (pid.equals(hid)) {
eachHop.remove();
return true;
}
}
return false;
}
/**
* Return a hop from the list of hops.
*
* @param pid peer id of the hop
* @return AccessPointAdvertisement of the corresponding hop
*/
public AccessPointAdvertisement getHop(PeerID pid) {
for (AccessPointAdvertisement hop : hops) {
PeerID hid = hop.getPeerID();
if (pid.equals(hid)) {
return hop.clone();
}
}
return null;
}
/**
* Alter the given newRoute (which does not start from here) by using firstLeg, a known route to whence
* it starts from. So that the complete route goes from here to the end-destination via firstLeg.
* public static boolean stichRoute(RouteAdvertisement newRoute,
*
* @param newRoute the new route
* @param firstLeg the first route
* @return true if successful
*/
public static boolean stichRoute(RouteAdvertisement newRoute, RouteAdvertisement firstLeg) {
return stichRoute(newRoute, firstLeg, null);
}
/**
* Alter the given newRoute (which does not start from here) by using firstLeg, a known route to whence
* it starts from. So that the complete route goes from here to the end-destination via firstLeg
* also shortcut the route by removing the local peer.
*
* @param newRoute the new route
* @param firstLeg first hop
* @param localPeer local PeerID
* @return true if successful
*/
public static boolean stichRoute(RouteAdvertisement newRoute, RouteAdvertisement firstLeg, PeerID localPeer) {
if (newRoute.hasALoop()) {
return false;
}
Vector<AccessPointAdvertisement> hops = newRoute.getVectorHops();
// Make room
hops.ensureCapacity(firstLeg.getVectorHops().size() + 1 + hops.size());
// prepend the routing peer unless the routing peer happens to be
// in the route already. That happens if the routing peer is the relay.
// or if the route does not have a first leg
PeerID routerPid = firstLeg.getDest().getPeerID();
if (newRoute.size() == 0 || (!newRoute.getFirstHop().getPeerID().equals(routerPid))) {
AccessPointAdvertisement ap = (AccessPointAdvertisement)
AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
// prepend the route with the routing peer.
ap.setPeerID(routerPid);
hops.add(0, ap);
}
// prepend the rest of the route
hops.addAll(0, firstLeg.getVectorHops());
// remove any loop from the root
cleanupLoop(newRoute, localPeer);
return true;
}
/**
* Remove loops from the route advertisement
* by shortcutting cycle from the route
*
* @param route the route advertisement
* @param localPeer local PeerID
*/
public static void cleanupLoop(RouteAdvertisement route, PeerID localPeer) {
// Note: we cleanup all enp addresses except for the last hop (which we
// use to shorten routes often enough).
// If we end-up removing the last hop, it means that it is the local
// peer and thus the route ends up with a size 0.
Vector<AccessPointAdvertisement> hops = route.getVectorHops();
Vector<AccessPointAdvertisement> newHops = new Vector<AccessPointAdvertisement>(hops.size());
AccessPointAdvertisement lastHop = null;
// Replace all by PID-only entries, but keep the last hop on the side.
if (!hops.isEmpty()) {
lastHop = hops.get(hops.size() - 1);
}
hops = (route.cloneOnlyPIDs()).getVectorHops();
// remove cycle from the route
for (int i = 0; i < hops.size(); i++) {
int loopAt = newHops.indexOf(hops.elementAt(i));
if (loopAt != -1) { // we found a cycle
// remove all entries after loopAt
for (int j = newHops.size(); --j > loopAt;) {
newHops.remove(j);
}
} else { // did not find it so we add it
newHops.add(hops.get(i));
}
}
// Remove the local peer in the route if we were given one
if (localPeer != null) {
for (int i = newHops.size(); --i >= 0;) {
if (localPeer.equals(newHops.elementAt(i).getPeerID())) {
// remove all the entries up to that point we
// need to keep the remaining of the route from that
// point
for (int j = 0; j <= i; j++) {
newHops.remove(0);
}
break;
}
}
}
if (lastHop != null && newHops.size() > 0) {
newHops.set(newHops.size() - 1, lastHop);
}
// update the new hops in the route
route.setHops(newHops);
}
}