/*
* 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.pipe;
import net.jxta.credential.Credential;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.MimeMediaType;
import net.jxta.document.StructuredDocumentFactory;
import net.jxta.document.StructuredTextDocument;
import net.jxta.document.XMLDocument;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.EndpointListener;
import net.jxta.endpoint.EndpointService;
import net.jxta.endpoint.OutgoingMessageEvent;
import net.jxta.id.ID;
import net.jxta.impl.cm.Srdi;
import net.jxta.impl.cm.Srdi.SrdiInterface;
import net.jxta.impl.cm.SrdiIndex;
import net.jxta.impl.protocol.PipeResolverMsg;
import net.jxta.impl.protocol.ResolverQuery;
import net.jxta.impl.protocol.SrdiMessageImpl;
import net.jxta.impl.resolver.InternalQueryHandler;
import net.jxta.impl.util.TimeUtils;
import net.jxta.impl.util.threads.TaskManager;
import net.jxta.logging.Logging;
import net.jxta.membership.MembershipService;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.InputPipe;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeService;
import net.jxta.protocol.PeerAdvertisement;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.protocol.PipeResolverMessage;
import net.jxta.protocol.PipeResolverMessage.MessageType;
import net.jxta.protocol.ResolverQueryMsg;
import net.jxta.protocol.ResolverResponseMsg;
import net.jxta.protocol.ResolverSrdiMsg;
import net.jxta.protocol.SrdiMessage;
import net.jxta.protocol.SrdiMessage.Entry;
import net.jxta.rendezvous.RendezVousService;
import net.jxta.rendezvous.RendezVousStatus;
import net.jxta.resolver.ResolverService;
import net.jxta.resolver.SrdiHandler;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class implements the Resolver interfaces for a PipeServiceImpl.
*/
class PipeResolver implements SrdiInterface, InternalQueryHandler, SrdiHandler, PipeRegistrar {
/**
* Logger
*/
private final static transient Logger LOG = Logger.getLogger(PipeResolver.class.getName());
private final static String PipeResolverName = "JxtaPipeResolver";
private final static String srdiIndexerFileName = "pipeResolverSrdi";
/**
* Local SRDI GC Interval
*/
private final static long GcDelay = 1 * TimeUtils.AMINUTE;
/**
* Constant for pipe event listeners to signify any query id.
*/
final static int ANYQUERY = 0;
/**
* The current query ID. The next value returned by {@link #getNextQueryID()}
* will be one greater than this value.
*/
private static int currentQueryID = 1;
/**
* Group we are working for
*/
private PeerGroup myGroup = null;
/**
* Group we are working for
*/
private EndpointService endpoint = null;
/**
* Resolver Service we will register with
*/
private ResolverService resolver = null;
/**
* The discovery service we will use
*/
private DiscoveryService discovery = null;
/**
* Membership Service we will use
*/
private MembershipService membership = null;
private Srdi srdi = null;
private Thread srdiThread = null;
private SrdiIndex srdiIndex = null;
private RendezVousService rendezvous = null;
/**
* The locally registered {@link net.jxta.pipe.InputPipe}s
*/
private final Map<ID, InputPipe> localInputPipes = new HashMap<ID, InputPipe>();
/**
* Registered listeners for pipe events.
*/
private final Map<ID, Map<Integer, Listener>> outputpipeListeners = new HashMap<ID, Map<Integer, Listener>>();
/**
* Encapsulates current Membership Service credential.
*/
final static class CurrentCredential {
/**
* The current default credential
*/
final Credential credential;
/**
* The current default credential in serialized XML form.
*/
final XMLDocument credentialDoc;
CurrentCredential(Credential credential, XMLDocument credentialDoc) {
this.credential = credential;
this.credentialDoc = credentialDoc;
}
}
/**
* The current Membership service default credential.
*/
CurrentCredential currentCredential;
/**
* Listener we use for membership property events.
*/
private class CredentialListener implements PropertyChangeListener {
/**
* Standard Constructor
*/
CredentialListener() {}
/**
* {@inheritDoc}
*/
public void propertyChange(PropertyChangeEvent evt) {
if (MembershipService.DEFAULT_CREDENTIAL_PROPERTY.equals(evt.getPropertyName())) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("New default credential event");
}
synchronized (PipeResolver.this) {
Credential cred = (Credential) evt.getNewValue();
XMLDocument credentialDoc;
if (null != cred) {
try {
credentialDoc = (XMLDocument) cred.getDocument(MimeMediaType.XMLUTF8);
currentCredential = new CurrentCredential(cred, credentialDoc);
} catch (Exception all) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Could not generate credential document", all);
}
currentCredential = null;
}
} else {
currentCredential = null;
}
}
}
}
}
final CredentialListener membershipCredListener = new CredentialListener();
/**
* A pipe resolver event.
*/
static class Event extends EventObject {
private final ID peerid;
private final ID pipeid;
private final String type;
private final int queryID;
/**
* Creates a new pipe resolution event
*
* @param source The PipeResolver generating the event.
* @param peerid The peer on which the pipe was found
* @param pipeid the pipe which was found
* @param type the type of pipe which was found
* @param queryid The query id associated with the response returned in this event
*/
public Event(PipeResolver source, ID peerid, ID pipeid, String type, int queryid) {
super(source);
this.peerid = peerid;
this.pipeid = pipeid;
this.type = type;
this.queryID = queryid;
}
/**
* Returns the peer associated with the event
*
* @return peerid
*/
public ID getPeerID() {
return peerid;
}
/**
* Returns the pipe associated with the event
*
* @return pipeid
*/
public ID getPipeID() {
return pipeid;
}
/**
* Returns the type of the pipe that is associated with the event
*
* @return type
*/
public String getType() {
return type;
}
/**
* Returns The query id associated with the response returned in this event
*
* @return query id associated with the response
*/
public int getQueryID() {
return queryID;
}
}
/**
* Pipe Resolver Event Listener. Implement this interface is you wish to
* Receive Pipe Resolver events.
*/
interface Listener extends EventListener {
/**
* Pipe Resolve event
*
* @param event event the PipeResolver Event
* @return true if the event was handled otherwise false
*/
boolean pipeResolveEvent(Event event);
/**
* A NAK Event was received for this pipe
*
* @param event event the PipeResolver Event
* @return true if the event was handled otherwise false
*/
boolean pipeNAKEvent(Event event);
}
/**
* return the next query id.
*
* @return the next eligible query id.
*/
static synchronized int getNextQueryID() {
currentQueryID++;
if (currentQueryID == Integer.MAX_VALUE) {
currentQueryID = 1;
}
return currentQueryID;
}
/**
* Constructor for the PipeResolver object
*
* @param peerGroup group for which this PipeResolver operates in
*/
PipeResolver(PeerGroup peerGroup) {
myGroup = peerGroup;
resolver = myGroup.getResolverService();
membership = myGroup.getMembershipService();
rendezvous = myGroup.getRendezVousService();
endpoint = myGroup.getEndpointService();
// Register to the Generic ResolverServiceImpl
resolver.registerHandler(PipeResolverName, this);
// start srdi
srdiIndex = new SrdiIndex(myGroup, srdiIndexerFileName, GcDelay);
srdi = new Srdi(myGroup, PipeResolverName, this, srdiIndex);
srdi.startPush(TaskManager.getTaskManager().getScheduledExecutorService(), 1 * TimeUtils.AYEAR);
resolver.registerSrdiHandler(PipeResolverName, this);
synchronized (this) {
// register our credential listener.
membership.addPropertyChangeListener(MembershipService.DEFAULT_CREDENTIAL_PROPERTY, membershipCredListener);
try {
// set the initial version of the default credential.
currentCredential = null;
Credential credential = membership.getDefaultCredential();
XMLDocument credentialDoc;
if (null != credential) {
credentialDoc = (XMLDocument) credential.getDocument(MimeMediaType.XMLUTF8);
currentCredential = new CurrentCredential(credential, credentialDoc);
}
} catch (Exception all) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "could not get default credential", all);
}
}
}
}
private boolean isRendezvous() {
if (rendezvous == null) {
rendezvous = myGroup.getRendezVousService();
}
RendezVousStatus mode = rendezvous.getRendezVousStatus();
return (mode == RendezVousStatus.RENDEZVOUS ||mode == RendezVousStatus.AUTO_RENDEZVOUS);
}
/**
* {@inheritDoc}
*/
public int processQuery(ResolverQueryMsg query) {
return processQuery(query, null);
}
/**
* {@inheritDoc}
*/
public int processQuery(ResolverQueryMsg query, EndpointAddress srcAddr) {
String queryFrom;
if (null != srcAddr) {
if ("jxta".equals(srcAddr.getProtocolName())) {
queryFrom = ID.URIEncodingName + ":" + ID.URNNamespace + ":" + srcAddr.getProtocolAddress();
} else {
// we don't know who routed us the query. Assume it came from the source.
queryFrom = query.getSrcPeer().toString();
}
} else {
// we don't know who routed us the query. Assume it came from the source.
queryFrom = query.getSrcPeer().toString();
}
String responseDest = query.getSrcPeer().toString();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Starting for :" + query.getQueryId() + " from " + srcAddr);
}
Reader queryReader = new StringReader(query.getQuery());
StructuredTextDocument doc = null;
try {
doc = (StructuredTextDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, queryReader);
} catch (IOException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "discarding malformed request ", e);
}
// no sense in re-propagation here
return ResolverService.OK;
} finally {
try {
queryReader.close();
} catch (IOException ignored) {
// ignored
}
queryReader = null;
}
PipeResolverMessage pipeQuery;
try {
pipeQuery = new PipeResolverMsg(doc);
} catch (IllegalArgumentException badDoc) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "discarding malformed request ", badDoc);
}
// no sense in re-propagation here
return ResolverService.OK;
} finally {
doc = null;
}
// is it a query?
if (!pipeQuery.getMsgType().equals(MessageType.QUERY)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("expected query - discarding.");
}
// no sense in re-propagation here
return ResolverService.OK;
}
// see if it is a query directed at our peer.
Set<ID> destPeers = pipeQuery.getPeerIDs();
boolean directedQuery = !destPeers.isEmpty();
boolean queryForMe = !directedQuery;
if (directedQuery) {
for (Object destPeer : destPeers) {
ID aPeer = (ID) destPeer;
if (aPeer.equals(myGroup.getPeerID())) {
queryForMe = true;
break;
}
}
if (!queryForMe) {
// It is an directed query, but request wasn't for this peer.
if (query.getSrcPeer().toString().equals(queryFrom)) {
// we only respond if the original src was not the query forwarder
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("discarding query. Query not for us.");
}
// tell the resolver no further action is needed.
return ResolverService.OK;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("responding to \'misdirected\' forwarded query.");
}
responseDest = queryFrom;
}
}
PeerID peerID = null;
if (queryForMe) {
// look locally.
InputPipe ip = findLocal((PipeID) pipeQuery.getPipeID());
if ((ip != null) && (ip.getType().equals(pipeQuery.getPipeType()))) {
peerID = myGroup.getPeerID();
}
}
if ((null == peerID) && !directedQuery) {
// This request was sent to everyone.
if (myGroup.isRendezvous()) {
// We are a RDV, allow the ResolverService to repropagate the query
List<PeerID> results = srdiIndex.query(pipeQuery.getPipeType(), PipeAdvertisement.IdTag,
pipeQuery.getPipeID().toString(), 20);
if (!results.isEmpty()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("forwarding query to " + results.size() + " peers");
}
srdi.forwardQuery(results, query);
// tell the resolver no further action is needed.
return ResolverService.OK;
}
// we don't know anything, continue the walk.
return ResolverService.Repropagate;
} else {
// We are an edge
if (query.getSrcPeer().toString().equals(queryFrom)) {
// we only respond if the original src was not the query forwarder
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("discarding query.");
}
// tell the resolver no further action is needed.
return ResolverService.OK;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("responding to query forwarded for \'misdirected\' query.");
}
responseDest = queryFrom;
}
}
// Build the answer
PipeResolverMessage pipeResp = new PipeResolverMsg();
pipeResp.setMsgType(MessageType.ANSWER);
pipeResp.setPipeID(pipeQuery.getPipeID());
pipeResp.setPipeType(pipeQuery.getPipeType());
if (null == peerID) {
// respond negative.
pipeResp.addPeerID(myGroup.getPeerID());
pipeResp.setFound(false);
} else {
pipeResp.addPeerID(peerID);
pipeResp.setFound(true);
pipeResp.setInputPeerAdv(myGroup.getPeerAdvertisement());
}
// make a response from the incoming query
ResolverResponseMsg res = query.makeResponse();
CurrentCredential current = currentCredential;
if (null != current) {
res.setCredential(current.credentialDoc);
}
res.setResponse(pipeResp.getDocument(MimeMediaType.XMLUTF8).toString());
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Sending answer for query \'" + query.getQueryId() + "\' to : " + responseDest);
}
resolver.sendResponse(responseDest, res);
return ResolverService.OK;
}
/**
* {@inheritDoc}
*/
public void processResponse(ResolverResponseMsg response) {
processResponse(response, null);
}
/**
* {@inheritDoc}
*/
public void processResponse(ResolverResponseMsg response, EndpointAddress srcAddr) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("got a response for \'" + response.getQueryId() + "\'");
}
Reader resp = new StringReader(response.getResponse());
StructuredTextDocument doc = null;
try {
doc = (StructuredTextDocument)
StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, resp);
} catch (Throwable e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "malformed response - discard", e);
}
return;
} finally {
try {
resp.close();
} catch (IOException ignored) {// ignored
}
resp = null;
}
PipeResolverMessage pipeResp;
try {
pipeResp = new PipeResolverMsg(doc);
} catch (Throwable caught) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "malformed response - discarding.", caught);
}
return;
} finally {
doc = null;
}
// check if it's a response.
if (!pipeResp.getMsgType().equals(MessageType.ANSWER)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("expected response - discarding.");
}
return;
}
PeerAdvertisement padv = pipeResp.getInputPeerAdv();
if ((null != padv) && !(myGroup.getPeerID().equals(padv.getPeerID()))) {
try {
// This is not our own peer adv so we keep it only for the default
// expiration time.
if (null == discovery) {
discovery = myGroup.getDiscoveryService();
}
if (null != discovery) {
discovery.publish(padv, DiscoveryService.DEFAULT_EXPIRATION, DiscoveryService.DEFAULT_EXPIRATION);
}
} catch (IOException ignored) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("could not publish peer adv");
}
}
}
String ipId = pipeResp.getPipeID().toString();
Set<ID> peerRsps = pipeResp.getPeerIDs();
for (Object peerRsp : peerRsps) {
// process each peer for which this response is about.
PeerID peer = (PeerID) peerRsp;
if (!pipeResp.isFound()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("NACK for pipe \'" + ipId + "\' from peer " + peer);
}
// We have received a NACK. Remove that entry.
srdiIndex.add(pipeResp.getPipeType(), PipeAdvertisement.IdTag, ipId, peer, 0);
} else {
long exp = getEntryExp(pipeResp.getPipeType(), PipeAdvertisement.IdTag, ipId, peer);
if ((PipeServiceImpl.VERIFYINTERVAL / 2) > exp) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Using Expiration " + (PipeServiceImpl.VERIFYINTERVAL / 2) + " which is > " + exp);
}
// create antry only if one does not exist,or entry exists with
// lesser lifetime
// cache the result for half the verify interval
srdiIndex.add(pipeResp.getPipeType(), PipeAdvertisement.IdTag, ipId, peer,
(PipeServiceImpl.VERIFYINTERVAL / 2));
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("DB Expiration " + exp + " > " + (PipeServiceImpl.VERIFYINTERVAL / 2)
+ " overriding attempt to decrease lifetime");
}
}
}
// call listener for pipeid
callListener(response.getQueryId(), pipeResp.getPipeID(), pipeResp.getPipeType(), peer, !pipeResp.isFound());
}
}
private long getEntryExp(String pkey, String skey, String value, PeerID peerid) {
List<SrdiIndex.Entry> list = srdiIndex.getRecord(pkey, skey, value);
for (SrdiIndex.Entry entry : list) {
if (entry.peerid.equals(peerid)) {
// exp in millis
return TimeUtils.toRelativeTimeMillis(entry.expiration);
}
}
return -1;
}
/**
* {@inheritDoc}
*/
public boolean processSrdi(ResolverSrdiMsg message) {
if (!isRendezvous()) {
// avoid caching in non rendezvous mode
return true;
}
if (message == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("no SRDI message");
}
return false;
}
if (message.getPayload() == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("empty SRDI message");
}
return false;
}
SrdiMessage srdiMsg;
try {
StructuredTextDocument asDoc = (StructuredTextDocument)
StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, new StringReader(message.getPayload()));
srdiMsg = new SrdiMessageImpl(asDoc);
} catch (Throwable e) {
// we don't understand this msg, let's skip it
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Invalid SRDI message", e);
}
return false;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Received an SRDI messsage with " + srdiMsg.getEntries().size() + " entries from " + srdiMsg.getPeerID());
}
for (Object o : srdiMsg.getEntries()) {
Entry entry = (Entry) o;
srdiIndex.add(srdiMsg.getPrimaryKey(), entry.key, entry.value, srdiMsg.getPeerID(), entry.expiration);
}
if (!PipeService.PropagateType.equals(srdiMsg.getPrimaryKey())) {
// don't replicate entries for propagate pipes. For unicast type
// pipes the replica is useful in finding pipe instances. Since
// walking rather than searching is done for propagate pipes this
// appropriate.
srdi.replicateEntries(srdiMsg);
}
return true;
}
/**
* {@inheritDoc}
*/
public void messageSendFailed(PeerID peerid, OutgoingMessageEvent e) {// so what.
}
/**
* {@inheritDoc}
*/
public void pushEntries(boolean all) {
pushSrdi((PeerID) null, all);
}
/**
* unregisters the resolver handler
*/
void stop() {
resolver.unregisterHandler(PipeResolverName);
resolver.unregisterSrdiHandler(PipeResolverName);
outputpipeListeners.clear();
// close the local pipes
List<InputPipe> openLocalPipes = new ArrayList<InputPipe>(localInputPipes.values());
for (InputPipe aPipe : openLocalPipes) {
try {
aPipe.close();
} catch (Exception failed) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Failure closing " + aPipe);
}
}
}
localInputPipes.clear();
srdiIndex.stop();
srdiIndex = null;
// stop the srdi thread
if (srdiThread != null) {
srdi.stop();
}
srdiThread = null;
srdi = null;
membership.removePropertyChangeListener("defaultCredential", membershipCredListener);
currentCredential = null;
// Avoid cross-reference problems with GC
myGroup = null;
resolver = null;
discovery = null;
membership = null;
}
/**
* {@inheritDoc}
*/
public boolean register(InputPipe ip) {
PipeID pipeID = (PipeID) ip.getPipeID();
synchronized (this) {
if (localInputPipes.containsKey(pipeID)) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Existing registered InputPipe for " + pipeID);
}
return false;
}
// Register this input pipe
boolean registered = endpoint.addIncomingMessageListener((EndpointListener) ip, "PipeService", pipeID.toString());
if (!registered) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Existing registered Endpoint Listener for " + pipeID);
}
return false;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Registering local InputPipe for " + pipeID);
}
localInputPipes.put(pipeID, ip);
}
// Announce the pipe to SRDI so that others will know we are listening.
pushSrdi(ip, true);
// Call anyone who may be listening for this input pipe.
callListener(0, pipeID, ip.getType(), myGroup.getPeerID(), false);
return true;
}
/**
* Return the local {@link net.jxta.pipe.InputPipe InputPipe}, if any, for the
* specified {@link net.jxta.pipe.PipeID PipeID}.
*
* @param pipeID the PipeID who's InputPipe is desired.
* @return The InputPipe object.
*/
public InputPipe findLocal(PipeID pipeID) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Find local InputPipe for " + pipeID);
}
// First look if the pipe is a local InputPipe
InputPipe ip = localInputPipes.get(pipeID);
// Found it.
if ((null != ip) && Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("found local InputPipe for " + pipeID);
}
return ip;
}
/**
* {@inheritDoc}
*/
public boolean forget(InputPipe pipe) {
PipeID pipeID = (PipeID) pipe.getPipeID();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Unregistering local InputPipe for " + pipeID);
}
// Unconditionally announce the change to SRDI.
// FIXME 20040529 bondolo This is overkill, it should be able to wait
// until the deltas are pushed.
pushSrdi(pipe, false);
InputPipe ip;
synchronized (this) {
ip = localInputPipes.remove(pipeID);
}
if (pipe != ip) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Pipe removed was not the same as the pipe to be removed!");
}
}
if (null != ip) {
// remove the queue for the general demux
EndpointListener removed = endpoint.removeIncomingMessageListener("PipeService", pipeID.toString());
if ((null == removed) || (pipe != removed)) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("removeIncomingMessageListener() did not remove correct pipe!");
}
}
}
return (ip != null);
}
/**
* Add a pipe resolver listener
*
* @param listener listener
* @param queryID The query this callback is being made in response to.
* @param pipeID The pipe which is the subject of the event.
* @return true if sucessfully added
*/
synchronized boolean addListener(ID pipeID, Listener listener, int queryID) {
Map<Integer, Listener> perpipelisteners = outputpipeListeners.get(pipeID);
// if no map for this pipeid, make one and add it to the top map.
if (null == perpipelisteners) {
perpipelisteners = new HashMap<Integer, Listener>();
outputpipeListeners.put(pipeID, perpipelisteners);
}
boolean alreadyThere = perpipelisteners.containsKey(queryID);
if (!alreadyThere) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("adding listener for " + pipeID + " / " + queryID);
}
perpipelisteners.put(queryID, listener);
}
return alreadyThere;
}
/**
* Call the listener for the specified pipe id informing it about the
* specified peer.
*
* @param qid The query this callback is being made in response to.
* @param pipeID The pipe which is the subject of the event.
* @param type The type of the pipe which is the subject of the event.
* @param peer The peer on which the remote input pipe was found.
* @param NAK indicate whether the event is a nack
*/
void callListener(int qid, ID pipeID, String type, PeerID peer, boolean NAK) {
Event newevent = new Event(this, peer, pipeID, type, qid);
boolean handled = false;
while (!handled) {
Listener pipeListener;
synchronized (this) {
Map<Integer, Listener> perpipelisteners = outputpipeListeners.get(pipeID);
if (null == perpipelisteners) {
if ((ANYQUERY != qid) && Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("No listener for pipe " + pipeID);
}
break;
}
pipeListener = perpipelisteners.get(qid);
}
if (null != pipeListener) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Calling Pipe resolver listener " + (NAK ? "NAK " : "") + "for " + pipeID);
}
try {
if (NAK) {
handled = pipeListener.pipeNAKEvent(newevent);
} else {
handled = pipeListener.pipeResolveEvent(newevent);
}
} catch (Throwable ignored) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING
,
"Uncaught Throwable in listener for: " + pipeID + "(" + pipeListener.getClass().getName() + ")", ignored);
}
}
}
// if we havent tried it already, try it with the ANYQUERY
if (ANYQUERY == qid) {
break;
}
qid = ANYQUERY;
}
}
/**
* Remove a pipe resolver listener
*
* @param pipeID listener to remove
* @param queryID matching queryid.
* @return listener object removed
*/
synchronized Listener removeListener(ID pipeID, int queryID) {
Map<Integer, Listener> perpipelisteners = outputpipeListeners.get(pipeID);
if (null == perpipelisteners) {
return null;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("removing listener for " + pipeID + " / " + queryID);
}
Listener removedListener = perpipelisteners.remove(queryID);
if (perpipelisteners.isEmpty()) {
outputpipeListeners.remove(pipeID);
}
return removedListener;
}
/**
* Send a request to find an input pipe
*
* @param adv the advertisement for the pipe we are seeking.
* @param acceptablePeers the set of peers at which we wish the pipe to
* be resolved. We will not accept responses from peers other than those
* in this set. Empty set means all peers are acceptable.
* @param queryID the query ID to use for the query. if zero then a query
* ID will be generated
* @return the query id under which the request was sent
*/
int sendPipeQuery(PipeAdvertisement adv, Set<? extends ID> acceptablePeers, int queryID) {
// choose a query id if non-prechosen.
if (0 == queryID) {
queryID = getNextQueryID();
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine((acceptablePeers.isEmpty() ? "Undirected" : "Directed") + " query (" + queryID + ") for " + adv.getPipeID());
}
Collection<? extends ID> targetPeers = new ArrayList<ID>(acceptablePeers);
// check local srdi to see if we have a potential answer
List<? extends ID> knownLocations = srdiIndex.query(adv.getType(), PipeAdvertisement.IdTag, adv.getPipeID().toString(), 100);
if (!knownLocations.isEmpty()) {
// we think we know where the pipe might be...
if (!acceptablePeers.isEmpty()) {
// only keep those peers which are acceptable.
knownLocations.retainAll(acceptablePeers);
}
// if the known locations contain any of the acceptable peers then
// we will send a directed query to ONLY those peers.
if (!knownLocations.isEmpty()) {
targetPeers = knownLocations;
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Using SRDI cache results for directed query (" + queryID + ") for " + adv.getPipeID());
}
}
}
// build the pipe query message.
PipeResolverMessage pipeQry = new PipeResolverMsg();
pipeQry.setMsgType(MessageType.QUERY);
pipeQry.setPipeID(adv.getPipeID());
pipeQry.setPipeType(adv.getType());
for (Object targetPeer : targetPeers) {
pipeQry.addPeerID((PeerID) targetPeer);
}
StructuredTextDocument asDoc = (StructuredTextDocument) pipeQry.getDocument(MimeMediaType.XMLUTF8);
// build the resolver query
ResolverQuery query = new ResolverQuery();
query.setHandlerName(PipeResolverName);
query.setQueryId(queryID);
query.setSrcPeer(myGroup.getPeerID());
query.setQuery(asDoc.toString());
CurrentCredential current = currentCredential;
if (null != current) {
query.setCredential(current.credentialDoc);
}
if (targetPeers.isEmpty()) {
// we have no idea, walk the tree
if (myGroup.isRendezvous()) {
// We are a rdv, then send it to the replica peer.
PeerID peer = srdi.getReplicaPeer(pipeQry.getPipeType() + PipeAdvertisement.IdTag + pipeQry.getPipeID().toString());
if (null != peer) {
srdi.forwardQuery(peer, query);
return queryID;
}
}
resolver.sendQuery(null, query);
} else {
// send it only to the peers whose result we would accept.
for (ID targetPeer : targetPeers) {
resolver.sendQuery(targetPeer.toString(), query);
}
}
return queryID;
}
/**
* {@inheritDoc}
*/
SrdiIndex getSrdiIndex() {
return srdiIndex;
}
/**
* {@inheritDoc}
* <p/>
* This implementation knows nothing of deltas, it just pushes it all.
*/
private void pushSrdi(PeerID peer, boolean all) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Pushing " + (all ? "all" : "deltas") + " SRDI to " + peer);
}
Map<String, List<Entry>> types = new HashMap<String, List<Entry>>();
synchronized (this) {
for (InputPipe ip : localInputPipes.values()) {
Entry entry = new Entry(PipeAdvertisement.IdTag, ip.getPipeID().toString(), Long.MAX_VALUE);
String type = ip.getType();
List<Entry> entries = types.get(type);
if (null == entries) {
entries = new ArrayList<Entry>();
types.put(type, entries);
}
entries.add(entry);
}
}
for (String type : types.keySet()) {
List<Entry> entries = types.get(type);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Sending a Pipe SRDI messsage in " + myGroup.getPeerGroupID() + " of " + entries.size() + " entries of type " + type);
}
SrdiMessage srdiMsg = new SrdiMessageImpl(myGroup.getPeerID(), 1, type, entries);
if (null == peer) {
srdi.pushSrdi(null, srdiMsg);
} else {
srdi.pushSrdi(peer, srdiMsg);
}
}
}
/**
* Push SRDI entry for the specified pipe
*
* @param ip the pipe who's entry we are pushing
* @param adding adding an entry for the pipe or expiring the entry?
*/
private void pushSrdi(InputPipe ip, boolean adding) {
srdiIndex.add(ip.getType(), PipeAdvertisement.IdTag, ip.getPipeID().toString(), myGroup.getPeerID(),
adding ? Long.MAX_VALUE : 0);
SrdiMessage srdiMsg;
try {
srdiMsg = new SrdiMessageImpl(myGroup.getPeerID(), 1, // ttl
ip.getType(), PipeAdvertisement.IdTag, ip.getPipeID().toString(), adding ? Long.MAX_VALUE : 0);
if (myGroup.isRendezvous()) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Replicating a" + (adding ? "n add" : " remove") + " Pipe SRDI entry for pipe [" + ip.getPipeID()
+ "] of type " + ip.getType());
}
srdi.replicateEntries(srdiMsg);
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Sending a" + (adding ? "n add" : " remove") + " Pipe SRDI messsage for pipe [" + ip.getPipeID()
+ "] of type " + ip.getType());
}
srdi.pushSrdi(null, srdiMsg);
}
} catch (Throwable e) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Uncaught throwable pushing SRDI entries", e);
}
}
}
}