2 Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
4 * The Sun Project JXTA(TM) Software License
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
16 * 3. The end-user documentation included with the redistribution, if any, must
17 * include the following acknowledgment: "This product includes software
18 * developed by Sun Microsystems, Inc. for JXTA(TM) technology."
19 * Alternately, this acknowledgment may appear in the software itself, if
20 * and wherever such third-party acknowledgments normally appear.
22 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
23 * not be used to endorse or promote products derived from this software
24 * without prior written permission. For written permission, please contact
25 * Project JXTA at http://www.jxta.org.
27 * 5. Products derived from this software may not be called "JXTA", nor may
28 * "JXTA" appear in their name, without prior written permission of Sun.
30 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
33 * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
37 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
39 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 * JXTA is a registered trademark of Sun Microsystems, Inc. in the United
42 * States and other countries.
44 * Please see the license information page at :
45 * <http://www.jxta.org/project/www/license.html> for instructions on use of
46 * the license in source files.
48 * ====================================================================
50 * This software consists of voluntary contributions made by many individuals
51 * on behalf of Project JXTA. For more information on Project JXTA, please see
52 * http://www.jxta.org.
54 * This license is based on the BSD license adopted by the Apache Foundation.
56 package net.jxta.protocol;
58 import net.jxta.document.AdvertisementFactory;
59 import net.jxta.document.ExtendableAdvertisement;
60 import net.jxta.endpoint.EndpointAddress;
61 import net.jxta.id.ID;
62 import net.jxta.id.IDFactory;
63 import net.jxta.peer.PeerID;
64 import net.jxta.peergroup.PeerGroupID;
66 import java.io.ByteArrayInputStream;
67 import java.io.InputStream;
68 import java.util.ArrayList;
69 import java.util.Collection;
70 import java.util.Enumeration;
71 import java.util.HashSet;
72 import java.util.Iterator;
73 import java.util.List;
74 import java.util.ListIterator;
76 import java.util.Vector;
79 * Advertisement used to represent a route to a peer. Routes are represented in
80 * a generic manner as a sequence of hops to reach the destination. Each hop
81 * represent a potential relay peer in the route:
91 * A route can have as many hops as necessary. Hops are implicitly ordered
92 * starting from the hop with the shortest route to reach the destination. If a
93 * peer cannot reach directly the dest, it should try to reach in descending
94 * order one of the listed hops. Some hops may have the same physical distance
95 * to the destination. Some hops may define alternative routes.
97 * The destination and hops are defined using a AccessPointAdvertisements.
99 * @see net.jxta.protocol.PeerAdvertisement
100 * @see net.jxta.protocol.AccessPointAdvertisement
102 public abstract class RouteAdvertisement extends ExtendableAdvertisement implements Cloneable {
104 public static final String DEST_PID_TAG = "DstPID";
107 * AccessPointAdvertisement of destination peer.
109 private transient AccessPointAdvertisement dest = (AccessPointAdvertisement)
110 AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
113 * Semi-ordered list of alternative hops to the destination.
115 private transient Vector<AccessPointAdvertisement> hops = new Vector<AccessPointAdvertisement>();
118 * Cached value for {@link #getID()}
120 private transient ID hashID = null;
123 * construct a new route
125 * <b>WARNING hops may be MODIFIED.</b>
127 * @param destPid destination
128 * @param firsthop first hop node ID
130 * @return the new route
132 public static RouteAdvertisement newRoute(PeerID destPid, PeerID firsthop, Vector<AccessPointAdvertisement> hops) {
134 if (destPid == null) {
135 throw new IllegalArgumentException("Missing destination peer id.");
138 for (AccessPointAdvertisement apa : hops) {
140 throw new IllegalArgumentException("Bad route. null APA.");
143 if (apa.getPeerID() == null) {
144 throw new IllegalArgumentException("Bad route. Incomplete APA.");
148 RouteAdvertisement route = (RouteAdvertisement)
149 AdvertisementFactory.newAdvertisement(RouteAdvertisement.getAdvertisementType());
151 route.setDestPeerID(destPid);
153 // set the route hops
156 // check if the given first hop is already in the route if not add it
157 // (note: we do not expect it to be there, but it is acceptable).
158 if (firsthop != null) {
159 AccessPointAdvertisement ap = route.getFirstHop();
161 if (ap == null || !ap.getPeerID().equals(firsthop)) {
162 ap = (AccessPointAdvertisement)
163 AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
164 ap.setPeerID(firsthop);
165 route.setFirstHop(ap);
176 public RouteAdvertisement clone() {
178 RouteAdvertisement a = (RouteAdvertisement) super.clone();
180 a.setDest(getDest());
182 // deep copy of the hops
183 Vector<AccessPointAdvertisement> clonehops = getVectorHops();
185 ListIterator<AccessPointAdvertisement> eachHop = clonehops.listIterator();
187 while (eachHop.hasNext()) {
188 eachHop.set(eachHop.next().clone());
191 a.setHops(clonehops);
194 } catch (CloneNotSupportedException impossible) {
195 throw new Error("Object.clone() threw CloneNotSupportedException", impossible);
200 * makes a copy of a route advertisement
201 * that only contains PID not endpoint addresses
203 * @return object clone route advertisement
205 public RouteAdvertisement cloneOnlyPIDs() {
206 RouteAdvertisement routeAdvertisement;
209 routeAdvertisement = (RouteAdvertisement) super.clone();
210 routeAdvertisement.setDestEndpointAddresses(new Vector<String>());
211 } catch (CloneNotSupportedException impossible) {
212 throw new Error("Object.clone() threw CloneNotSupportedException", impossible);
215 // deep copy of the hops
216 Vector<AccessPointAdvertisement> clonehops = routeAdvertisement.getVectorHops();
218 ListIterator<AccessPointAdvertisement> eachHop = clonehops.listIterator();
220 while (eachHop.hasNext()) {
221 AccessPointAdvertisement aHop = eachHop.next();
223 eachHop.set(aHop.clone());
226 routeAdvertisement.setHops(clonehops);
227 return routeAdvertisement;
231 * Compare if two routes are equals. Equals means same destination with the
232 * same endpoint addresses and thee same number of hops and the same
233 * endpoint addresses for each hop.
235 * @param target the route to compare against
236 * @return boolean true if the route is equal to this route otherwise false
239 public boolean equals(Object target) {
241 if (this == target) {
245 if (!(target instanceof RouteAdvertisement)) {
249 RouteAdvertisement route = (RouteAdvertisement) target;
251 // check the destination
252 if (!dest.equals(route.getDest())) {
256 // check each of the hops
258 // routes need to have the same size
259 if (hops.size() != route.size()) {
265 for (AccessPointAdvertisement hop : route.hops) {
266 if (!hop.equals(hops.get(index++))) {
278 public int hashCode() {
279 if (null != dest.getPeerID()) {
280 return dest.getPeerID().hashCode();
282 // force all incomplete advertisements to hash to the same place.
288 * Returns the identifying type of this Advertisement.
290 * @return String the type of advertisement
292 public static String getAdvertisementType() {
300 public final String getBaseAdvType() {
301 return getAdvertisementType();
308 public synchronized ID getID() {
309 if (null == dest.getPeerID()) {
310 throw new IllegalStateException("Destination peerID not defined. Incomplete RouteAdvertisement");
313 if (hashID == null) {
315 // We have not yet built it. Do it now
316 byte[] seed = getAdvertisementType().getBytes("UTF-8");
317 InputStream in = new ByteArrayInputStream(dest.getPeerID().toString().getBytes("UTF-8"));
319 hashID = IDFactory.newCodatID((PeerGroupID) dest.getPeerID().getPeerGroupID(), seed, in);
320 } catch (Exception ez) {
328 * Returns the route destination Peer ID
330 * @return peerID of the destination of the route
332 public PeerID getDestPeerID() {
333 return dest.getPeerID();
337 * Sets the route destination peer id.
339 * @param pid route destination peerID
341 public void setDestPeerID(PeerID pid) {
342 if ((null != pid) && (null != dest.getPeerID()) && (!pid.equals(dest.getPeerID()))) {
343 throw new IllegalStateException("Changing the peer id of the destination APA." + pid + " != " + dest.getPeerID());
349 synchronized (this) {
355 * Returns the destination access point. <b>This does <i>NOT</i> copy
356 * the AccessPointAdvertisement</b>.
358 * @return AccessPointAdvertisement of the destination peer.
359 * @deprecated Because this method unsafely exposes destination AccessPointAdvertisement it will be removed.
362 public AccessPointAdvertisement getDest() {
367 * Sets the access point of the destination. <b>This does <i>NOT</i> copy
368 * the AccessPointAdvertisement</b>.
370 * @param ap AccessPointAdvertisement of the destination peer
372 public void setDest(AccessPointAdvertisement ap) {
373 PeerID destPid = dest.getPeerID();
375 this.dest = ap.clone();
377 if ((null != destPid) && (null != dest.getPeerID()) && (!destPid.equals(dest.getPeerID()))) {
378 throw new IllegalStateException("Changed the peer id of the destination APA." + destPid + " != " + dest.getPeerID());
381 if (null != destPid) {
382 dest.setPeerID(destPid);
386 synchronized (this) {
392 * Add a new list of EndpointAddresses to the Route Destination access
395 * @param addresses vector of endpoint addresses to add to the
396 * destination access point. Warning: The vector of endpoint addresses
397 * is specified as a vector of String. Each string representing
398 * one endpoint address.
399 * @deprecated Use {@link #addDestEndpointAddresses(List<EndpointAddress>)} instead.
402 public void addDestEndpointAddresses(Vector<String> addresses) {
403 dest.addEndpointAddresses(addresses);
407 * Clears all endpoint addresses associated with the destination peer.
409 public void clearDestEndpointAddresses() {
410 dest.clearEndpointAddresses();
414 * Add the specified endpoint address to destination peer.
416 * @param addr EndpointAddress to add.
418 public void addDestEndpointAddress(EndpointAddress addr) {
419 dest.addEndpointAddress(addr);
423 * Add all of the specified endpoint addresses to destination peer.
425 * @param addrs EndpointAddresses to add.
427 public void addDestEndpointAddresses(List<EndpointAddress> addrs) {
428 dest.addEndpointAddresses(addrs);
432 * Remove the specified endpoint address to destination peer.
434 * @param addr EndpointAddress to add.
436 public void removeDestEndpointAddress(EndpointAddress addr) {
437 dest.removeEndpointAddress(addr);
441 * Remove the specified endpoint addresses from destination peer.
443 * @param addrs EndpointAddress to add.
445 public void removeDestEndpointAddresses(Collection<EndpointAddress> addrs) {
446 dest.removeEndpointAddresses(addrs);
450 * Remove a list of EndpointAddresses from the Route Destination
453 * @param addresses vector of endpoint addresses to remove from the
454 * destination access point.
455 * @deprecated Use {@link #removeDestEndpointAddresses(Collection)}.
458 public void removeDestEndpointAddresses(Vector<String> addresses) {
459 dest.removeEndpointAddresses(addresses);
463 * Returns the endpoint addresses of the destination peer in their
466 * @return The {@code EndpointAddress}es of the destination peer.
468 public List<EndpointAddress> getDestEndpointAddresses() {
469 List<EndpointAddress> result = new ArrayList<EndpointAddress>();
471 Enumeration<String> eachEA = dest.getEndpointAddresses();
473 while (eachEA.hasMoreElements()) {
474 result.add(new EndpointAddress(eachEA.nextElement()));
481 * Set the route destination endpoint addresses
483 * @param ea vector of endpoint addresses. Warning: The vector is not copied
484 * and is used directly.
485 * @deprecated Use {@link #addDestEndpointAddress(EndpointAddress)} instead.
488 public void setDestEndpointAddresses(Vector<String> ea) {
489 dest.setEndpointAddresses(ea);
493 * returns the list of hops
495 * @return Enumeration list of hops as AccessPointAdvertisement
497 public Enumeration<AccessPointAdvertisement> getHops() {
498 return hops.elements();
502 * returns the list of hops
504 * @return Vector list of hops as AccessPointAdvertisement
506 public Vector<AccessPointAdvertisement> getVectorHops() {
511 * Sets the list of hops associated with this route.
513 * @param newHops AccessPointAdvertisements which form the hops. The
514 * Vector is <b>NOT</b> copied.
516 public void setHops(Vector<AccessPointAdvertisement> newHops) {
517 // It is legal to set it to null but it is automatically converted
518 // to an empty vector. The member hops is NEVER null.
519 if (null == newHops) {
520 hops = new Vector<AccessPointAdvertisement>();
522 for (AccessPointAdvertisement hop : newHops) {
523 if (null == hop.getPeerID()) {
524 throw new IllegalArgumentException("Bad hop");
533 * Check if the route contains the following hop
535 * @param pid peer id of the hop
536 * @return boolean true or false if the hop is found in the route
538 public boolean containsHop(PeerID pid) {
539 for (AccessPointAdvertisement hop : hops) {
540 PeerID hid = hop.getPeerID();
542 if (pid.equals(hid)) {
550 * Returns the AccessPointAdvertisement of first hop. <b>The
551 * AccessPointAdvertisement is <i>not</i> cloned.</b>
553 * @return AccessPointAdvertisement of first hop.
555 public AccessPointAdvertisement getFirstHop() {
556 return hops.isEmpty() ? null : hops.firstElement();
560 * Sets the AccessPointAdvertisement for the first hop. <b>The
561 * AccessPointAdvertisement is <i>not</i> cloned.</b>
563 * @param ap AccessPointAdvertisement of the first hop.
565 public void setFirstHop(AccessPointAdvertisement ap) {
566 if (null == ap.getPeerID()) {
567 throw new IllegalArgumentException("Bad hop");
574 * Returns the access point for the last hop. <b>The
575 * AccessPointAdvertisement is <i>not</i> cloned.</b>
577 * @return AccessPointAdvertisement last hop.
579 public AccessPointAdvertisement getLastHop() {
580 return hops.isEmpty() ? null : hops.lastElement();
584 * Sets the AccessPointAdvertisement of the last hop. <b>The
585 * AccessPointAdvertisement is <i>not</i> cloned.</b>
587 * @param ap AccessPointAdvertisement of the last hop.
589 public void setLastHop(AccessPointAdvertisement ap) {
590 if (null == ap.getPeerID()) {
591 throw new IllegalArgumentException("Bad hop");
598 * check if the route has a loop
600 * @return boolean true or false if the route has a loop
602 public boolean hasALoop() {
603 // Now check for any other potential loops.
605 Set<PeerID> seenPeers = new HashSet<PeerID>(hops.size());
607 for (AccessPointAdvertisement anAPA : hops) {
608 PeerID pid = anAPA.getPeerID();
610 if (seenPeers.contains(pid)) {
611 return true; // There is a loop.
620 * return the length of the route
622 * @return int size of the route
629 * Return the hop that follows the specified currentHop. <b>The
630 * AccessPointAdvertisement is <i>not</i> cloned.</b>
632 * @param currentHop PeerID of the current hop
633 * @return ap AccessPointAdvertisement of the next Hop
635 public AccessPointAdvertisement nextHop(PeerID currentHop) {
637 // check if we have a real route
638 if (hops.isEmpty()) {
643 // find the index of the route
645 boolean found = false;
647 for (AccessPointAdvertisement ap : hops) {
648 if (currentHop.equals(ap.getPeerID())) {
655 AccessPointAdvertisement nextHop = null;
658 // The peer is not into the list. Since we have got that message,
659 // the best we can do is to send it to the first gateway in the
661 nextHop = hops.get(0);
663 // Found the peer within the vector of hops. Get the next hop.
664 if (index < hops.size()) {
665 nextHop = hops.get(index);
673 * Generate a string that displays the route
674 * information for logging or debugging purpose
676 * @return String return a string containing the route info
678 public String display() {
679 StringBuilder routeBuf = new StringBuilder();
681 routeBuf.append("Dest APA : ");
682 AccessPointAdvertisement dest = getDest();
684 routeBuf.append(dest.display());
685 routeBuf.append("\n");
688 Enumeration<AccessPointAdvertisement> e = getHops();
690 while (e.hasMoreElements()) {
691 AccessPointAdvertisement hop = e.nextElement();
694 routeBuf.append("HOPS = ");
696 routeBuf.append("\n\t[").append(i++).append("] ");
698 routeBuf.append(hop.display());
700 return routeBuf.toString();
704 * Remove a hop from the list of hops.
706 * @param pid peer id of the hop
707 * @return boolean true or false if the hop is found in the route
709 public boolean removeHop(PeerID pid) {
710 Iterator<AccessPointAdvertisement> eachHop = hops.iterator();
712 while (eachHop.hasNext()) {
713 AccessPointAdvertisement hop = eachHop.next();
714 PeerID hid = hop.getPeerID();
716 if (pid.equals(hid)) {
726 * Return a hop from the list of hops.
728 * @param pid peer id of the hop
729 * @return AccessPointAdvertisement of the corresponding hop
731 public AccessPointAdvertisement getHop(PeerID pid) {
732 for (AccessPointAdvertisement hop : hops) {
733 PeerID hid = hop.getPeerID();
735 if (pid.equals(hid)) {
743 * Alter the given newRoute (which does not start from here) by using firstLeg, a known route to whence
744 * it starts from. So that the complete route goes from here to the end-destination via firstLeg.
745 * public static boolean stichRoute(RouteAdvertisement newRoute,
747 * @param newRoute the new route
748 * @param firstLeg the first route
749 * @return true if successful
751 public static boolean stichRoute(RouteAdvertisement newRoute, RouteAdvertisement firstLeg) {
752 return stichRoute(newRoute, firstLeg, null);
756 * Alter the given newRoute (which does not start from here) by using firstLeg, a known route to whence
757 * it starts from. So that the complete route goes from here to the end-destination via firstLeg
758 * also shortcut the route by removing the local peer.
760 * @param newRoute the new route
761 * @param firstLeg first hop
762 * @param localPeer local PeerID
763 * @return true if successful
765 public static boolean stichRoute(RouteAdvertisement newRoute, RouteAdvertisement firstLeg, PeerID localPeer) {
767 if (newRoute.hasALoop()) {
771 Vector<AccessPointAdvertisement> hops = newRoute.getVectorHops();
774 hops.ensureCapacity(firstLeg.getVectorHops().size() + 1 + hops.size());
776 // prepend the routing peer unless the routing peer happens to be
777 // in the route already. That happens if the routing peer is the relay.
778 // or if the route does not have a first leg
779 PeerID routerPid = firstLeg.getDest().getPeerID();
781 if (newRoute.size() == 0 || (!newRoute.getFirstHop().getPeerID().equals(routerPid))) {
782 AccessPointAdvertisement ap = (AccessPointAdvertisement)
783 AdvertisementFactory.newAdvertisement(AccessPointAdvertisement.getAdvertisementType());
785 // prepend the route with the routing peer.
786 ap.setPeerID(routerPid);
790 // prepend the rest of the route
791 hops.addAll(0, firstLeg.getVectorHops());
793 // remove any loop from the root
794 cleanupLoop(newRoute, localPeer);
799 * Remove loops from the route advertisement
800 * by shortcutting cycle from the route
802 * @param route the route advertisement
803 * @param localPeer local PeerID
805 public static void cleanupLoop(RouteAdvertisement route, PeerID localPeer) {
807 // Note: we cleanup all enp addresses except for the last hop (which we
808 // use to shorten routes often enough).
809 // If we end-up removing the last hop, it means that it is the local
810 // peer and thus the route ends up with a size 0.
812 Vector<AccessPointAdvertisement> hops = route.getVectorHops();
813 Vector<AccessPointAdvertisement> newHops = new Vector<AccessPointAdvertisement>(hops.size());
814 AccessPointAdvertisement lastHop = null;
816 // Replace all by PID-only entries, but keep the last hop on the side.
817 if (!hops.isEmpty()) {
818 lastHop = hops.get(hops.size() - 1);
820 hops = (route.cloneOnlyPIDs()).getVectorHops();
822 // remove cycle from the route
823 for (int i = 0; i < hops.size(); i++) {
824 int loopAt = newHops.indexOf(hops.elementAt(i));
826 if (loopAt != -1) { // we found a cycle
828 // remove all entries after loopAt
829 for (int j = newHops.size(); --j > loopAt;) {
832 } else { // did not find it so we add it
833 newHops.add(hops.get(i));
837 // Remove the local peer in the route if we were given one
838 if (localPeer != null) {
839 for (int i = newHops.size(); --i >= 0;) {
840 if (localPeer.equals(newHops.elementAt(i).getPeerID())) {
841 // remove all the entries up to that point we
842 // need to keep the remaining of the route from that
844 for (int j = 0; j <= i; j++) {
852 if (lastHop != null && newHops.size() > 0) {
853 newHops.setElementAt(lastHop, newHops.size() - 1);
856 // update the new hops in the route
857 route.setHops(newHops);