/*
* 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.rendezvous;
import net.jxta.endpoint.*;
import net.jxta.id.ID;
import net.jxta.impl.endpoint.EndpointUtils;
import net.jxta.impl.util.TimeUtils;
import net.jxta.logging.Logger;
import net.jxta.logging.Logging;
import net.jxta.peergroup.PeerGroup;
import net.jxta.protocol.PeerAdvertisement;
import net.jxta.protocol.RouteAdvertisement;
import java.io.IOException;
/**
* Manages a connection with a remote client or a rendezvous peer.
*/
public abstract class PeerConnection implements OutgoingMessageEventListener {
private final static transient Logger LOG = Logging.getLogger(PeerConnection.class.getName());
protected final PeerGroup group;
protected final EndpointService endpoint;
/**
* ID of the remote peer.
*/
protected final ID peerid;
/**
* Cached name of the peer for display purposes.
*/
protected String peerName = null;
/**
* If true then we believe we are still connected to the remote peer.
*/
protected volatile boolean connected = true;
/**
* The absolute time in milliseconds at which we expect this connection to
* expire unless renewed.
*/
protected long leasedTil = -1;
/**
* A cached messenger to be used for sending messages to the remote peer.
*/
protected Messenger cachedMessenger = null;
/**
* Constructor for the PeerConnection object
*
* @param group group context
* @param endpoint the endpoint service to use for sending messages.
* @param peerid destination peerid
*/
public PeerConnection(PeerGroup group, EndpointService endpoint, ID peerid) {
this.group = group;
this.endpoint = endpoint;
this.peerid = peerid;
this.peerName = peerid.toString();
}
/**
* {@inheritDoc}
* <p/>
* <p/>performs PeerID comparison
*/
@Override
public boolean equals(Object obj) {
return obj instanceof PeerConnection && peerid.equals(((PeerConnection) obj).peerid);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return peerid.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return getPeerName() + (connected ? " C" : " c") + " : " + Long.toString(TimeUtils.toRelativeTimeMillis(leasedTil));
}
/**
* {@inheritDoc}
*/
public void messageSendFailed(OutgoingMessageEvent event) {
// If it's just a case of queue overflow, ignore it, report warning
if (event.getFailure() == null) {
final StringBuilder builder = createLogMessage(event);
LOG.warn(builder.toString());
return;
}
setConnected(false);
}
private StringBuilder createLogMessage(OutgoingMessageEvent event)
{
final StringBuilder builder = new StringBuilder();
builder.append("Message send failed ").append(event).append(" ");
Message msg = (Message)event.getSource();
final Message.ElementIterator messageElements = msg.getMessageElements();
while(messageElements.hasNext())
{
final MessageElement element = messageElements.next();
builder.append(element.getElementName()).append(",");
}
return builder;
}
/**
* {@inheritDoc}
*/
public void messageSendSucceeded(OutgoingMessageEvent event) {// hurray!
}
/**
* Get the peer id of the peer associated with this connection.
*
* @return The peer id of the connected peer.
*/
public ID getPeerID() {
return peerid;
}
/**
* Get the peer name. If the symbolic name is available, use it,
* otherwise returns the peer id.
*
* @return The name of the connected peer.
*/
public String getPeerName() {
return peerName;
}
/**
* set the peer name.
*
* @param name the peer name
*/
protected void setPeerName(String name) {
peerName = name;
}
/**
* Set the lease duration in relative milliseconds.
*
* @param leaseDuration the lease duration in relative milliseconds.
*/
protected void setLease(long leaseDuration) {
leasedTil = TimeUtils.toAbsoluteTimeMillis(leaseDuration);
}
/**
* Time at which the lease will expire in absolute milliseconds.
*
* @return The lease value
*/
public long getLeaseEnd() {
return leasedTil;
}
/**
* Declare that we are connected for the specified amount of time.
*
* @param leaseDuration The duration of the lease in relative milliseconds.
*/
protected void connect(long leaseDuration) {
setLease(leaseDuration);
setConnected(true);
}
/**
* Test if the connection is still active.
*
* @return The connected value
*/
public boolean isConnected() {
if ( connected ) {
connected = (TimeUtils.toRelativeTimeMillis(leasedTil) >= 0);
}
return connected;
}
/**
* Set the connection state. This operation must be idempotent.
*
* @param isConnected The new connected state. Be very careful when
* setting <code>true</code> state without setting a new lease.
*/
public void setConnected(boolean isConnected) {
connected = isConnected;
}
// /**
// * Return a messenger suitable for communicating to this peer.
// *
// * @return a messenger for sending to this peer or <code>null</code> if
// * none is available.
// * @deprecated Preferred style is to pass the connection object around and
// * use the sendMessage method rather than getting the messenger.
// */
// @Deprecated
// protected Messenger getCachedMessenger() {
//
// // We don't do the check on existing messenger under synchronization
// // hence the temporary variable.
// Messenger result = cachedMessenger;
//
// if ((null == result) || result.isClosed()) {
// // We need a new messenger.
// PeerAdvertisement padv = null;
//
// DiscoveryService discovery = group.getDiscoveryService();
//
// // Try to see if we have a peer advertisement for this peer.
// // This is very likely.
// if (null != discovery) {
// try {
// Enumeration each = discovery.getLocalAdvertisements(DiscoveryService.PEER, "PID", peerid.toString());
//
// if (each.hasMoreElements()) {
// padv = (PeerAdvertisement) each.nextElement();
// }
// } catch (Exception ignored) {
// //ignored
// }
// }
// result = getCachedMessenger(padv);
// }
//
// return result;
// }
/**
* Return a messenger suitable for communicating to this peer.
*
* @param padv A peer advertisement which will be used for route hints if
* a new messenger is needed.
* @return a messenger for sending to this peer or <code>null</code> if
* none is available.
*/
protected synchronized Messenger getCachedMessenger(PeerAdvertisement padv) {
if ((null != padv) && !peerid.equals(padv.getPeerID())) {
throw new IllegalArgumentException("Peer Advertisement does not match connection");
}
if ((null != padv) && (null != padv.getName())) {
setPeerName(padv.getName());
}
// if we have a good messenger then re-use it.
if ((null != cachedMessenger) && !cachedMessenger.isClosed()) {
return cachedMessenger;
}
cachedMessenger = null;
if (isConnected()) {
// we only get new messengers while we are connected. It is not
// worth the effort for a disconnected peer. We WILL use an existing
// open messenger if we have one though.
Logging.logCheckedDebug(LOG, "Getting new cached Messenger for ", peerName);
RouteAdvertisement hint = null;
if (null != padv)
hint = EndpointUtils.extractRouteAdv(padv);
EndpointAddress destAddress = new EndpointAddress(peerid, null, null);
cachedMessenger = endpoint.getMessenger(destAddress, hint);
if (null == cachedMessenger) {
// no messenger? avoid doing more work.
setConnected(false);
}
} else {
Logging.logCheckedDebug(LOG, "connection closed : NOT getting new cached Messenger for ", peerName);
}
return cachedMessenger;
}
/**
* Send a message to the remote peer.
*
* @param msg the message to send.
* @param service The destination service.
* @param param Parameters for the destination service.
* @return <true>true</true> if the message was queued to be sent,
* otherwise <code>false</code>. A <code>true</code> result does not mean
* that the destination peer will receive the message.
*/
public boolean sendMessage(Message msg, String service, String param) {
Messenger messenger = cachedMessenger;
if (null != messenger) {
messenger.sendMessage(msg, service, param, this);
return true;
} else {
return false;
}
}
/**
* Send a message to remote peer, blocking until on network or connection failed
*
* @param msg the message to send
* @param service the destination service
* @param param Parameters for the destination service.
* @return <true>true</true> if the message has been queued for send and should succeed if connection maintained.
*/
public boolean sendMessageB(Message msg, String service, String param)
{
Messenger messenger = cachedMessenger;
if (null != messenger) {
try
{
messenger.sendMessageB(msg, service, param);
}
catch (IOException e)
{
LOG.warn("Failed to send blocking message owing to IOException " + e);
}
return true;
} else {
return false;
}
}
}