/*
* 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.relay;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.MimeMediaType;
import net.jxta.document.StructuredDocumentFactory;
import net.jxta.document.StructuredDocumentUtils;
import net.jxta.document.StructuredTextDocument;
import net.jxta.document.XMLDocument;
import net.jxta.document.XMLElement;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.EndpointService;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.MessageReceiver;
import net.jxta.endpoint.MessageSender;
import net.jxta.endpoint.MessageTransport;
import net.jxta.endpoint.Messenger;
import net.jxta.endpoint.MessengerState;
import net.jxta.endpoint.MessengerStateListener;
import net.jxta.id.ID;
import net.jxta.impl.protocol.RelayConfigAdv;
import net.jxta.impl.util.SeedingManager;
import net.jxta.impl.util.TimeUtils;
import net.jxta.impl.util.URISeedingManager;
import net.jxta.logging.Logging;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.protocol.AccessPointAdvertisement;
import net.jxta.protocol.PeerAdvertisement;
import net.jxta.protocol.RdvAdvertisement;
import net.jxta.protocol.RouteAdvertisement;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* RelayClient manages the relationship with the RelayServer(s)
*
*/
public class RelayClient implements MessageReceiver, Runnable {
/**
* Logger
*/
private final static transient Logger LOG = Logger.getLogger(RelayClient.class.getName());
private final static long DEFAULT_EXPIRATION = 20L * TimeUtils.AMINUTE;
private final PeerGroup group;
private final String serviceName;
private EndpointService endpoint;
private final EndpointAddress publicAddress;
private final String groupName;
private final String peerId;
private final int maxServers;
private final long leaseLengthToRequest;
private final long messengerPollInterval;
private Thread thread = null;
private volatile boolean closed = false;
/**
* The peergroups which want notification when we connect to a relay.
*/
private final List<PeerGroup> activeRelayListeners = new ArrayList<PeerGroup>();
/**
* The currently connected relays.
*/
private final Map<EndpointAddress, RouteAdvertisement> activeRelays = new Hashtable<EndpointAddress, RouteAdvertisement>();
/**
* Our source for relay servers.
*/
private final SeedingManager seedingManager;
RelayServerConnection currentServer = null;
public RelayClient(PeerGroup group, String serviceName, RelayConfigAdv relayConfig) {
this.group = group;
this.groupName = group.getPeerGroupID().getUniqueValue().toString();
this.serviceName = serviceName;
maxServers = (-1 != relayConfig.getMaxRelays()) ? relayConfig.getMaxRelays() : RelayTransport.DEFAULT_MAX_SERVERS;
leaseLengthToRequest = (-1 != relayConfig.getClientLeaseDuration())
? relayConfig.getClientLeaseDuration()
: RelayTransport.DEFAULT_LEASE;
messengerPollInterval = (-1 != relayConfig.getMessengerPollInterval())
? relayConfig.getMessengerPollInterval()
: RelayTransport.DEFAULT_POLL_INTERVAL;
URISeedingManager uriSeedingManager = new URISeedingManager(relayConfig.getAclUri(), relayConfig.getUseOnlySeeds(), group, serviceName);
for (EndpointAddress aSeeder : Arrays.asList(relayConfig.getSeedRelays())) {
uriSeedingManager.addSeed(aSeeder.toURI());
}
for (URI aSeed : Arrays.asList(relayConfig.getSeedingURIs())) {
uriSeedingManager.addSeedingURI(aSeed);
}
this.seedingManager = uriSeedingManager;
// sanity check
peerId = group.getPeerID().getUniqueValue().toString();
publicAddress = new EndpointAddress(RelayTransport.protocolName, peerId, null, null);
if (Logging.SHOW_CONFIG && LOG.isLoggable(Level.CONFIG)) {
StringBuilder configInfo = new StringBuilder("Configuring Relay Client");
configInfo.append("\n\tGroup Params :");
configInfo.append("\n\t\tGroup : ").append(group.getPeerGroupName());
configInfo.append("\n\t\tGroup ID : ").append(group.getPeerGroupID());
configInfo.append("\n\t\tPeer ID : ").append(group.getPeerID());
configInfo.append("\n\tConfiguration :");
configInfo.append("\n\t\tService Name : ").append(serviceName);
configInfo.append("\n\t\tPublic Address : ").append(publicAddress);
configInfo.append("\n\t\tMax Relay Servers : ").append(maxServers);
configInfo.append("\n\t\tMax Lease Length : ").append(leaseLengthToRequest).append("ms.");
configInfo.append("\n\t\tMessenger Poll Interval : ").append(messengerPollInterval).append("ms.");
LOG.config(configInfo.toString());
}
}
public synchronized boolean startClient() {
endpoint = group.getEndpointService();
if (endpoint.addMessageTransport(this) == null) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Transport registration refused");
}
return false;
}
// start the client thread
thread = new Thread(group.getHomeThreadGroup(), this, "Relay Client Worker Thread for " + publicAddress);
thread.setDaemon(true);
thread.start();
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("Started client : " + publicAddress.toString());
}
return true;
}
public synchronized void stopClient() {
if (closed) {
return;
}
closed = true;
endpoint.removeMessageTransport(this);
// make sure the thread is not running
Thread tempThread = thread;
thread = null;
if (tempThread != null) {
tempThread.interrupt();
}
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("Stopped client : " + publicAddress.toString());
}
}
/**
* {@inheritDoc}
*/
public Iterator<EndpointAddress> getPublicAddresses() {
return Collections.singletonList(publicAddress).iterator();
}
/**
* {@inheritDoc}
*/
public String getProtocolName() {
return RelayTransport.protocolName;
}
/**
* {@inheritDoc}
*/
public EndpointService getEndpointService() {
return endpoint;
}
/**
* {@inheritDoc}
*/
public Object transportControl(Object operation, Object Value) {
return null;
}
/**
* Logic for the relay client
*
* <ol>
* <li>Pick a relay server to try</li>
* <li>try getting a messenger to relay server, if can not get messenger, start over</li>
* <li>use the messenger to send a connect message</li>
* <li> wait for a response, if there is no response or a disconnect response, start over</li>
* <li>while still connected
* <ol>
* <li>renew the lease as needed and keep the messenger connected</li>
* <ol></li>
* </ol>
*
* <p/>FIXME 20041102 bondolo The approach used here is really, really
* stupid. The calls to <code>connectToRelay()</code> will not return if a
* connection to a relay is achieved. This makes continued iteration over
* seeds after return incredibly silly. <code>connectToRelay()</code> only
* returns when it can <b>NO LONGER CONNECT</b> to the relay. The only
* hack I can think of to subvert this is to stop iteration of advs/seeds
* if <code>connectToRelay()</code> takes a long time. bizarre.
*/
public void run() {
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("Start relay client thread");
}
try {
long nextConnectAttemptAt = 0;
RdvAdvertisement referral = null;
List<RouteAdvertisement> allSeeds = null;
long gotLastSeedsAt = 0;
// run until the service is stopped
while (!closed) {
// Attempt to use any referral immediately.
if (null != referral) {
RouteAdvertisement relayRoute = referral.getRouteAdv();
relayRoute.setDestPeerID(referral.getPeerID());
referral = connectToRelay(new RelayServerConnection(this, relayRoute));
continue;
}
// Sleep until it is time for the next connection attempt.
long untilNextConnectAttempt = TimeUtils.toRelativeTimeMillis(nextConnectAttemptAt);
if (untilNextConnectAttempt > 0) {
try {
Thread.sleep(untilNextConnectAttempt);
} catch (InterruptedException e) {
Thread.interrupted();
continue;
}
}
// Don't allow next connection attempt to start any sooner than this.
nextConnectAttemptAt = TimeUtils.toAbsoluteTimeMillis(30 * TimeUtils.ASECOND);
// Get seeds if we need them or the ones we have are old.
if ((TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), gotLastSeedsAt) > (5 * TimeUtils.AMINUTE))
|| allSeeds.isEmpty()) {
allSeeds = new ArrayList<RouteAdvertisement>(Arrays.asList(seedingManager.getActiveSeedRoutes()));
gotLastSeedsAt = TimeUtils.timeNow();
}
// Try seeds until we get a connection, a referral or are closed.
while ((null == referral) && !allSeeds.isEmpty() && !closed) {
RouteAdvertisement aSeed = allSeeds.remove(0);
if (null == aSeed.getDestPeerID()) {
// It is an incomplete route advertisement. We are going to assume that it is only a wrapper for a single ea.
Vector<String> seed_eas = aSeed.getDest().getVectorEndpointAddresses();
if (!seed_eas.isEmpty()) {
EndpointAddress aSeedHost = new EndpointAddress(seed_eas.get(0));
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Attempting relay connect to : " + aSeedHost);
}
referral = connectToRelay(new RelayServerConnection(this, aSeedHost));
}
} else {
// We have a full route, send it to the virtual address of the route!
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Attempting relay connect to : " + aSeed.getDestPeerID());
}
referral = connectToRelay(new RelayServerConnection(this, aSeed));
}
}
}
} catch (Throwable all) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "Uncaught Throwable in thread :" + Thread.currentThread().getName(), all);
}
} finally {
thread = null;
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("stop client thread");
}
}
}
protected boolean isRelayConnectDone() {
return (thread == null || Thread.currentThread() != thread);
}
/**
* @param server The relay server to connect to
* @return The advertisement of an alternate relay server to try.
*/
RdvAdvertisement connectToRelay(RelayServerConnection server) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Connecting to " + server);
}
RdvAdvertisement referral = null;
// make this the current server
currentServer = server;
// try getting a messenger to the relay peer
if (!server.createMessenger(leaseLengthToRequest)) {
return referral;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("got messenger " + server);
}
// check the peerId of the relay peer
if (server.logicalAddress != null && "jxta".equals(server.logicalAddress.getProtocolName())) {
server.peerId = server.logicalAddress.getProtocolAddress();
}
// make sure that the peerId was found.
if (server.peerId == null) {
if (server.messenger != null) {
server.sendDisconnectMessage();
server.messenger.close();
}
return referral;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("got peerId " + server);
}
synchronized (this) {
// wait for a response from the server
// There is no real damage other than bandwidth usage in sending
// a message on top of the connection request, so we realy do not
// wait very long before doing it.
long requestTimeoutAt = TimeUtils.toAbsoluteTimeMillis(5 * TimeUtils.ASECOND);
while (currentServer != null && currentServer.leaseLength == 0 && !isRelayConnectDone()) {
long waitTimeout = requestTimeoutAt - System.currentTimeMillis();
if (waitTimeout <= 0) {
// did not receive the response in time ?
break;
}
try {
wait(waitTimeout);
} catch (InterruptedException e) {
// ignore interrupt
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "wait got interrupted early ", e);
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("wait done");
}
}
}
if (currentServer == null) {
return server.alternateRelayAdv;
}
if (isRelayConnectDone()) {
if (currentServer.messenger != null) {
currentServer.messenger.close();
}
currentServer = null;
return server.alternateRelayAdv;
}
// If we did not get a lease in the first 5 secs, maybe it is because
// the server knows us from a previous session. Then it will wait for
// a lease renewal message before responding, not just the connection.
// Send one and wait another 15.
if (currentServer.leaseLength == 0) {
currentServer.sendConnectMessage(leaseLengthToRequest);
synchronized (this) {
// wait for a response from the server
long requestTimeoutAt = TimeUtils.toAbsoluteTimeMillis(15 * TimeUtils.ASECOND);
while (currentServer != null && currentServer.leaseLength == 0 && !isRelayConnectDone()) {
long waitTimeout = requestTimeoutAt - System.currentTimeMillis();
if (waitTimeout <= 0) {
// did not receive the response in time ?
break;
}
try {
wait(waitTimeout);
} catch (InterruptedException e) {
// ignore interrupt
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "wait got interrupted early ", e);
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("wait done");
}
}
}
}
// If we had a messenger but are going to give up that relay server because it is
// not responsive or rejected us. Make sure that the messenger is closed.
if (currentServer == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("did not get connect from " + server);
}
// return any alternate relay advertisements
return server.alternateRelayAdv;
}
if (currentServer.relayAdv == null || currentServer.leaseLength == 0 || isRelayConnectDone()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("did not get connect from " + server);
}
if (currentServer.messenger != null) {
currentServer.sendDisconnectMessage();
currentServer.messenger.close();
}
currentServer = null;
// return any alternate relay advertisements
return server.alternateRelayAdv;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Connected to " + server);
}
RouteAdvertisement holdAdv = server.relayAdv;
EndpointAddress holdDest = server.logicalAddress;
// register this relay server
addActiveRelay(holdDest, holdAdv);
// maintain the relay server connection
referral = maintainRelayConnection(server);
// unregister this relay server
removeActiveRelay(holdDest, holdAdv);
return referral;
}
// FIXME: jice@jxta.org 20030212. This is junk code: that should be a
// method of RelayServerConnection and at least not refer to currentServer
// other than to assign the reference.
protected RdvAdvertisement maintainRelayConnection(RelayServerConnection server) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("maintainRelayConnection() start " + currentServer);
}
if (server == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RelayConnection() failed at start " + currentServer);
}
return null;
}
synchronized (this) {
long currentTime = System.currentTimeMillis();
long renewLeaseAt = currentServer.leaseObtainedAt + currentServer.leaseLength / 3;
long waitTimeout = 0;
// This will be true if we need to do the first lease renewal early
// (that is at the time of the next connection check).
// We'll do that if we did not know the relay server's adv (seed).
// In that case we told the relay server to send us its own
// adv, else we told it to send us some alternate adv (we have to
// chose). In the former case, we want to do a lease connect
// request soon so that the server has an opportunity to send us
// the alternate adv that we did not get during initial connection.
boolean earlyRenew = currentServer.seeded;
while (currentServer != null && !isRelayConnectDone()) {
// calculate how long to wait
waitTimeout = renewLeaseAt - currentTime;
// check that the waitTimeout is not greater than the messengerPollInterval
// We want to make sure that we poll. Most of the time it cost nothing.
// Also, if we urgently need to renew our lease we may wait
// less, but if we fail to get our lease renewed in time, the
// delay may become negative. In that case we do not want
// to start spinning madly. The only thing we can do is just
// wait some arbitrary length of time for the lease to be
// renewed. (If that gets badly overdue, we should probably
// give up on that relay server, though).
if (waitTimeout > messengerPollInterval || waitTimeout <= 0) {
waitTimeout = messengerPollInterval;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("waitTimeout=" + waitTimeout + " server=" + currentServer);
}
try {
wait(waitTimeout);
} catch (InterruptedException e) {
Thread.interrupted();
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("wait done, server=" + currentServer);
}
// make sure the server did not disconnect while waiting
if (currentServer == null) {
break;
}
// get the current time
currentTime = System.currentTimeMillis();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("check messenger " + currentServer);
}
// check if the messenger is still open
if (currentServer.messenger.isClosed()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Server connection broken");
}
// See if we can re-open, that happens often.
// That's a reason to renew the connection,
// Not a reason to give up on the server yet.
// Note we do not renew the lease. This is a transient
// and if the server forgot about us, it will respond
// to the connection alone. Otherwise, we'd rather avoid
// getting a response, since in some cases http connections
// close after each received message.
if (!currentServer.createMessenger(currentServer.leaseLength)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Server connection NOT re-established");
}
// lost connection to relay server
currentServer = null;
break;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Server connection re-established");
}
// getMessenger asks for a new lease.
// In the meantime, we'll just assume our old lease is
// still current and that the messenger breakage was just
// a transient.
if (!isRelayConnectDone()) {
continue;
}
}
// We've been asked to leave. Be nice and tell the
// server about it.
if (isRelayConnectDone()) {
break;
}
// check if the lease needs to be renewed
renewLeaseAt = currentServer.leaseObtainedAt + currentServer.leaseLength / 3;
if (currentTime >= renewLeaseAt || earlyRenew) {
earlyRenew = false;
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("renew lease " + currentServer);
}
// If we do not receive any response to our lease renewals
// (that is the response is overdue badly), then we give
// up and try another relayServer. We give up after 4 minutes
// because if we go as far as 5 we start overshooting other
// timeouts such as the local peer becoming a rdv in a sub-group.
// This later timeout is usually set to 5 minutes or more.
if ((currentTime > currentServer.leaseObtainedAt + currentServer.leaseLength / 3 + 4 * TimeUtils.AMINUTE)
|| (!currentServer.sendConnectMessage(leaseLengthToRequest))) {
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("renew lease failed" + currentServer);
}
if (currentServer.messenger != null) {
currentServer.messenger.close();
}
currentServer.messenger = null;
currentServer.peerId = null;
currentServer.leaseLength = 0;
currentServer.leaseObtainedAt = 0;
currentServer.relayAdv = null;
currentServer = null;
break;
}
}
}
}
if (isRelayConnectDone() && currentServer != null) {
currentServer.sendDisconnectMessage();
if (currentServer.messenger != null) {
currentServer.messenger.close();
}
currentServer.messenger = null;
currentServer.peerId = null;
currentServer.leaseLength = 0;
currentServer.leaseObtainedAt = 0;
currentServer.relayAdv = null;
// Make sure that we will not suggest an alternate
// since we're asked to terminate.
currentServer.alternateRelayAdv = null;
currentServer = null;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("maintainRelayConnection() terminated " + currentServer);
}
return server.alternateRelayAdv;
}
protected synchronized void handleResponse(Message message, EndpointAddress dstAddr) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("handleResponse " + currentServer);
}
// ignore all responses if there is not a current server
if (currentServer == null) {
return;
}
// get the request, make it lowercase so that case is ignored
String response = RelayTransport.getString(message, RelayTransport.RESPONSE_ELEMENT);
if (response == null) {
return;
}
response = response.toLowerCase();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("response = " + response);
}
// check if a relay advertisement was included
RdvAdvertisement relayAdv = null;
MessageElement advElement = message.getMessageElement(RelayTransport.RELAY_NS, RelayTransport.RELAY_ADV_ELEMENT);
if (null != advElement) {
try {
XMLDocument asDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(advElement);
Advertisement adv = AdvertisementFactory.newAdvertisement(asDoc);
if (adv instanceof RdvAdvertisement) {
relayAdv = (RdvAdvertisement) adv;
}
} catch (IOException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Could not read Relay RdvAdvertisement", e);
}
}
}
// WATCHOUT: this is not a pid, just the unique string portion.
String serverPeerId = dstAddr.getServiceParameter();
// only process the request if a client peer id was sent
if (serverPeerId == null) {
return;
}
// ignore all responses that are not from the current server
if (!serverPeerId.equals(currentServer.peerId)) {
return;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("serverPeerId = " + serverPeerId);
}
// Figure out which response it is
if (RelayTransport.CONNECTED_RESPONSE.equals(response)) {
// Connect Response
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("connected response for " + currentServer);
}
String responseLeaseString = RelayTransport.getString(message, RelayTransport.LEASE_ELEMENT);
long responseLease = 0;
if (responseLeaseString != null) {
try {
responseLease = Long.parseLong(responseLeaseString);
} catch (NumberFormatException e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "could not parse response lease string", e);
}
}
}
// make sure the lease is valid
if (responseLease <= 0) {
// invalid lease value
return;
}
// update the lease values
currentServer.leaseLength = responseLease;
currentServer.leaseObtainedAt = System.currentTimeMillis();
// Since we got the lease, if we requested a queue flush, it's
// now done. We never send it with a new messenger creation, but
// when the server already has us as a client it does not respond
// to connections through messenger creation, so we're sure we
// will have to send an explicit connect message before we get
// a response. So, we're sure it's done if it was needed.
currentServer.flushNeeded = false;
if (relayAdv != null) {
// Set it only if it is the server's own. Else it got
// published. Still set alternateRelayAdv so that we
// can return something that could be usefull when this
// connection breaks.
PeerID pidOfAdv = relayAdv.getPeerID();
String pidOfAdvUnique = pidOfAdv.getUniqueValue().toString();
if (currentServer.peerId.equals(pidOfAdvUnique)) {
currentServer.relayAdv = relayAdv.getRouteAdv();
// Fix the embedded route adv !
currentServer.relayAdv.setDestPeerID(pidOfAdv);
} else {
currentServer.alternateRelayAdv = relayAdv;
}
}
notifyAll();
} else if (RelayTransport.DISCONNECTED_RESPONSE.equals(response)) {
// Disconnect Response
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("disconnected from " + currentServer);
}
// If our request was denied, the adv that came back is
// always an alternate one.
currentServer.alternateRelayAdv = relayAdv;
if (currentServer.messenger != null) {
currentServer.messenger.close();
}
currentServer.messenger = null;
currentServer.peerId = null;
currentServer.leaseLength = 0;
currentServer.leaseObtainedAt = 0;
currentServer.relayAdv = null;
currentServer = null;
notifyAll();
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("response handled for " + currentServer);
}
}
static class RelayServerConnection {
final RelayClient client;
Messenger messenger = null;
EndpointAddress logicalAddress = null;
String peerId = null;
long leaseLength = 0;
long leaseObtainedAt = 0;
// If seeded out of a raw address, we have relayAddress.
// relayAdv comes only later.
public RouteAdvertisement relayAdv = null;
EndpointAddress relayAddress = null;
RdvAdvertisement alternateRelayAdv = null;
boolean seeded = false;
boolean flushNeeded = true; // true until we know it's been done
private final MessengerStateListener failureListener = new MessengerStateListener() {
public boolean messengerStateChanged(int newState) {
if((newState & Messenger.TERMINAL) != 0) {
// wake up the relay client, so that it will attempt to
// re-establish the connection or switch to a different
// relay server.
synchronized(client) {
client.notifyAll();
}
}
return true;
}
};
protected RelayServerConnection(RelayClient client, EndpointAddress addr) {
this.client = client;
relayAddress = new EndpointAddress(addr, null, null);
seeded = true;
}
protected RelayServerConnection(RelayClient client, RouteAdvertisement relayAdv) {
this.client = client;
this.relayAdv = relayAdv;
}
protected boolean createMessenger(long leaseLengthToRequest) {
// make sure the old messenger is closed
if (messenger != null) {
messenger.close();
messenger = null;
}
List<String> endpointAddresses = null;
// check for a relay advertisement
if (relayAdv != null) {
AccessPointAdvertisement accessPointAdv = relayAdv.getDest();
if (accessPointAdv != null) {
endpointAddresses = accessPointAdv.getVectorEndpointAddresses();
}
} else {
// silly but if we use getVetorEndpointAddresses, we get
// strings. It's realy simpler to have only one kind of obj
// inthere.
endpointAddresses = new ArrayList<String>(1);
endpointAddresses.add(relayAddress.toString());
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("createMessenger to " + endpointAddresses);
}
// make sure we found some endpoint addresses to try
if (endpointAddresses == null) {
return false;
}
// try each endpoint address until one is successful
for (String s : endpointAddresses) {
if (s == null) {
continue;
}
EndpointAddress addr = new EndpointAddress(s);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("find transport for " + addr);
}
// get the list of messengers on this endpoint
Iterator transports = client.endpoint.getAllMessageTransports();
while (transports.hasNext() && messenger == null) {
MessageTransport transport = (MessageTransport) transports.next();
// only try transports that are senders and allow routing
if (transport instanceof MessageSender && ((MessageSender) transport).allowsRouting()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("try transport " + transport);
}
if (addr.getProtocolName().equals(transport.getProtocolName())) {
// NOTE: here we're creating a messenger.
// For risk management reason, we refrain from
// including the flush request at this time in
// this. There is the possibility that the
// connection will be repeatedly established
// by the transport in our bakck, and would keep
// including the flush request ! Normaly this
// does not matter because the server should
// disregard it when it come in that way, but
// still, let's be defensive. We will still send
// the flush in a subsequent explicit message.
String reqStr = RelayTransport.createConnectString(leaseLengthToRequest, relayAdv == null, false);
// NOTE: this is simulating address mangling by CrossgroupMessenger.
// The real service param is after the "/" in the below serviceParam arg.
EndpointAddress addrToUse = new EndpointAddress(addr, "EndpointService:" + client.groupName
,
client.serviceName + "/" + reqStr);
messenger = ((MessageSender) transport).getMessenger(addrToUse, null);
if (messenger != null && messenger.isClosed()) {
messenger = null;
}
if (messenger != null) {
logicalAddress = messenger.getLogicalDestinationAddress();
// We're using a known adv, which means that
// we did not ask to get the adv back.
// Make sure that we do not keep going with
// an adv for the wrong peer. That can happen.
if (relayAdv != null && !RelayTransport.addr2pid(logicalAddress).equals(relayAdv.getDestPeerID())) {
// oops, wrong guy !
messenger.close();
messenger = null;
logicalAddress = null;
} else {
messenger.addStateListener(failureListener);
}
// In case it was not given, set relayAddress
// for toString purposes.
relayAddress = addr;
}
}
}
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("messenger=" + messenger);
}
return (messenger != null);
}
protected boolean sendConnectMessage(long leaseLengthToRequest) {
if (messenger == null || messenger.isClosed()) {
return false;
}
Message message = RelayTransport.createConnectMessage(leaseLengthToRequest, (relayAdv == null), flushNeeded);
try {
messenger.sendMessage(message, "EndpointService:" + client.groupName, client.serviceName + "/" + client.peerId);
} catch (IOException e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "could not send connect message", e);
}
// connection attempt failed
return false;
}
return true;
}
protected boolean sendDisconnectMessage() {
if (messenger == null || messenger.isClosed()) {
return false;
}
Message message = RelayTransport.createDisconnectMessage();
try {
messenger.sendMessage(message, "EndpointService:" + client.groupName, client.serviceName + "/" + client.peerId);
} catch (IOException e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "could not send disconnect message", e);
}
// connection attempt failed
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return
((relayAddress == null) ? "(adv to " + relayAdv.getDestPeerID() + ")" : relayAddress.toString()) + " ["
+ leaseLength + ", " + leaseObtainedAt + "] ";
}
}
/**
* Register an active Relay to the endpoint. This is done
* so the Route Advertisement of the PeerAdvertisement is
* updated
*/
public synchronized boolean addActiveRelayListener(PeerGroup service) {
boolean added = false;
if (!activeRelayListeners.contains(service)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Register group to relay connection " + service.getPeerGroupName());
}
activeRelayListeners.add(service);
added = true;
}
return added;
}
/**
* Unregister an active Relay to the endpoint. This is done
* so the Route Advertisement of the PeerAdvertisement is
* updated
*/
public synchronized boolean removeActiveRelayListener(PeerGroup service) {
activeRelayListeners.remove(service);
return true;
}
/**
* Notify of a new relay connection
*
*/
public synchronized boolean addActiveRelay(EndpointAddress address, RouteAdvertisement relayRoute) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("notify add relay connection for " + address);
}
// need to notify all our listeners
for (PeerGroup pg : activeRelayListeners) {
addRelay(pg, relayRoute);
}
// maintain the list of active relays
activeRelays.put(address, relayRoute);
return true;
}
/**
* Notify of a relay connection removal
*
*/
public synchronized boolean removeActiveRelay(EndpointAddress address, RouteAdvertisement relayRoute) {
// need to notify all our listeners
for (PeerGroup pg : activeRelayListeners) {
removeRelay(pg, relayRoute);
}
activeRelays.remove(address);
return true;
}
/**
* Register an active Relay to the endpoint. This is done
* so the Route Advertisement of the PeerAdvertisement is
* updated
*
* @param relayRoute address of the relay to add
*/
private void addRelay(PeerGroup pg, RouteAdvertisement relayRoute) {
ID assignedID = PeerGroup.endpointClassID;
try {
// get the advertisement of the associated endpoint address as we
// need to get the peer Id and available route
// update our own peer advertisement
PeerAdvertisement padv = pg.getPeerAdvertisement();
XMLDocument myParam = (XMLDocument) padv.getServiceParam(assignedID);
RouteAdvertisement route;
if (myParam == null) {
// we should have found a route here. This is not good
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("no route found in peer adv");
}
return;
} else {
Enumeration<XMLElement> paramChilds = myParam.getChildren(RouteAdvertisement.getAdvertisementType());
XMLElement param = null;
if (paramChilds.hasMoreElements()) {
param = paramChilds.nextElement();
}
route = (RouteAdvertisement) AdvertisementFactory.newAdvertisement(param);
}
if (route == null) { // we should have a route here
return;
}
// ready to stich the Relay route in our route advertisement
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("found route info for local peer \n" + route.display());
}
// update the new hops info
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("OLD route info to local peer \n" + route.display());
}
// If we already have the relay in our list of hops, remove it.
// The new version can only be more accurate.
route.removeHop(relayRoute.getDestPeerID());
// Get a hold of the hops list AFTER removing: removeHop
// rebuilds the vector !
Vector<AccessPointAdvertisement> hops = route.getVectorHops();
// Create the new relay Hop
hops.add(relayRoute.getDest());
// update the new hops info
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("NEW route info to local peer" + route.display());
}
// create the new param route
myParam = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, "Parm");
StructuredTextDocument xptDoc = (StructuredTextDocument)
route.getDocument(MimeMediaType.XMLUTF8);
StructuredDocumentUtils.copyElements(myParam, myParam, xptDoc);
padv.putServiceParam(assignedID, myParam);
// publish the new peer advertisement
DiscoveryService discovery = pg.getDiscoveryService();
if (discovery != null) {
discovery.publish(padv, DiscoveryService.DEFAULT_LIFETIME, DiscoveryService.DEFAULT_EXPIRATION);
}
} catch (Exception ex) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "exception adding relay route ", ex);
}
}
}
/**
* remove relay hop from the peer advertisement
*
* @param group which peer advertisement needs to be updated
* @param relayRoute address of the relay to be removed
*/
private void removeRelay(PeerGroup group, RouteAdvertisement relayRoute) {
// we can keep the advertisement for now (should remove it)
// remove the relay from its active list
ID assignedID = PeerGroup.endpointClassID;
PeerID relayPid = relayRoute.getDestPeerID();
try {
// get the advertisement of the associated endpoint address as we
// need to get the peer Id and available route
PeerAdvertisement padv;
// update our peer advertisement
padv = group.getPeerAdvertisement();
XMLDocument myParam = (XMLDocument) padv.getServiceParam(assignedID);
RouteAdvertisement route = null;
if (myParam == null) {
// no route found we should really have one
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("no route found in peer adv");
return;
}
} else {
Enumeration<XMLElement> paramChilds = myParam.getChildren(RouteAdvertisement.getAdvertisementType());
XMLElement param = null;
if (paramChilds.hasMoreElements()) {
param = paramChilds.nextElement();
}
route = (RouteAdvertisement) AdvertisementFactory.newAdvertisement( param);
}
if (route == null) {
return;
} // we should have a route here
// update the new hops info
route.removeHop(relayPid);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("new route info to the peer" + route.display());
}
// create the new param route
myParam = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, "Parm");
XMLDocument xptDoc = (XMLDocument) route.getDocument(MimeMediaType.XMLUTF8);
StructuredDocumentUtils.copyElements(myParam, myParam, xptDoc);
padv.putServiceParam(assignedID, myParam);
// publish the new advertisement
DiscoveryService discovery = group.getDiscoveryService();
if (discovery != null) {
discovery.publish(padv, DiscoveryService.DEFAULT_LIFETIME, DiscoveryService.DEFAULT_EXPIRATION);
}
} catch (Throwable theMatter) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Failed adding relay route", theMatter);
}
}
}
/**
* return the list of connected relays
*/
public Vector<AccessPointAdvertisement> getActiveRelays(PeerGroup pg) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("get active Relays list");
}
Vector<AccessPointAdvertisement> hops = new Vector<AccessPointAdvertisement>();
for (RouteAdvertisement route : activeRelays.values()) {
try {
// publish our route if pg is not null
if (pg != null) {
DiscoveryService discovery = pg.getDiscoveryService();
if (discovery != null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("publishing route to active relay " + route.display());
}
discovery.publish(route, DEFAULT_EXPIRATION, DEFAULT_EXPIRATION);
}
}
} catch (Exception ex) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "error publishing active relay", ex);
}
continue;
}
hops.add(route.getDest());
}
return hops;
}
}