/* * 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.discovery; import net.jxta.credential.Credential; import net.jxta.discovery.DiscoveryEvent; import net.jxta.discovery.DiscoveryListener; 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.XMLDocument; import net.jxta.endpoint.EndpointAddress; import net.jxta.endpoint.OutgoingMessageEvent; import net.jxta.exception.PeerGroupException; import net.jxta.id.ID; import net.jxta.impl.cm.CacheManager; import net.jxta.impl.cm.SrdiManager; import net.jxta.impl.cm.Srdi; import net.jxta.impl.peergroup.StdPeerGroup; import net.jxta.impl.protocol.DiscoveryConfigAdv; import net.jxta.impl.protocol.DiscoveryQuery; import net.jxta.impl.protocol.DiscoveryResponse; import net.jxta.impl.protocol.ResolverQuery; import net.jxta.impl.protocol.ResolverResponse; import net.jxta.impl.protocol.SrdiMessageImpl; import net.jxta.impl.resolver.InternalQueryHandler; import net.jxta.impl.util.TimeUtils; import net.jxta.logging.Logger; import net.jxta.logging.Logging; import net.jxta.membership.MembershipService; import net.jxta.peer.PeerID; import net.jxta.peergroup.PeerGroup; import net.jxta.platform.Module; import net.jxta.protocol.*; import net.jxta.rendezvous.RendezVousService; import net.jxta.rendezvous.RendezvousEvent; import net.jxta.rendezvous.RendezvousListener; 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.InputStream; import java.io.StringReader; import java.net.URI; import java.text.MessageFormat; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** * This Discovery Service implementation provides a mechanism to discover * Advertisements using the Resolver service and SRDI. * <p/> * This implementation uses the standard JXTA Peer Discovery Protocol (PDP). * <p/> * The DiscoveryService service also provides a way to obtain information * from a specified peer and request other peer advertisements, this method is * particularly useful in the case of a portal where new relationships may be * established starting from a predetermined peer (perhaps described in address * book, or through an invitation). * * @see net.jxta.discovery.DiscoveryService * @see net.jxta.protocol.DiscoveryQueryMsg * @see net.jxta.impl.protocol.DiscoveryQuery * @see net.jxta.protocol.DiscoveryResponseMsg * @see net.jxta.impl.protocol.DiscoveryResponse * @see net.jxta.resolver.ResolverService * @see <a href="https://jxta-spec.dev.java.net/nonav/JXTAProtocols.html#proto-pdp" target="_blank">JXTA Protocols Specification : Peer Discovery Protocol</a> */ public class DiscoveryServiceImpl implements DiscoveryService, InternalQueryHandler, RendezvousListener, SrdiHandler, SrdiManager.SrdiPushEntriesInterface { private final static Logger LOG = Logging.getLogger(DiscoveryServiceImpl.class.getName()); /** * adv types */ final static String[] dirname = {"Peers", "Groups", "Adv"}; /** * The Query ID which will be associated with remote publish operations. */ private final static int REMOTE_PUBLISH_QUERYID = 0; private final static String srdiIndexerFileName = "discoverySrdi"; /** * The current discovery query ID. static to make debugging easier. */ private final static AtomicInteger qid = new AtomicInteger(0); /** * The maximum number of responses we will return for ANY query. */ private final static int MAX_RESPONSES = 50; /** * The cache manager we're going to use to cache jxta advertisements */ protected CacheManager cm; private PeerGroup group = null; /** * assignedID as a String. */ private String handlerName = null; private ModuleImplAdvertisement implAdvertisement = null; private ResolverService resolver = null; private RendezVousService rendezvous = null; private MembershipService membership = null; private PeerID localPeerId = null; private boolean localonly = false; private boolean alwaysUseReplicaPeer = false; private boolean forwardBelowThreshold = false; private boolean stopped = true; /** * The table of global discovery listeners. */ private final Set<DiscoveryListener> listeners = new HashSet<DiscoveryListener>(); /** * The table of discovery query listeners. */ private final Map<Integer, DiscoveryListener> queryListeners = new HashMap<Integer, DiscoveryListener>(); private final String checkPeerAdvLock = "Check/Update PeerAdvertisement Lock"; private PeerAdvertisement lastPeerAdv = null; private int lastModCount = -1; private boolean isRdv = false; private Srdi srdiIndex = null; private SrdiManager srdiManager = null; private long runInterval = 30 * TimeUtils.ASECOND; /** * 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 credential change events. */ private class CredentialListener implements PropertyChangeListener { /** * {@inheritDoc} */ public void propertyChange(PropertyChangeEvent evt) { if (MembershipService.DEFAULT_CREDENTIAL_PROPERTY.equals(evt.getPropertyName())) { Logging.logCheckedDebug(LOG, "New default credential event"); synchronized (DiscoveryServiceImpl.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) { Logging.logCheckedWarning(LOG, "Could not generate credential document\n", all); currentCredential = null; } } else { currentCredential = null; } } } } } /** * Our listener for membership credential change events. */ private final CredentialListener membershipCredListener = new CredentialListener(); // /** // * {@inheritDoc} // * // * @since 2.6 This method has been deprecated and now returns {@code this} rather than // * an instance of {@code DiscoveryServiceInterface}. It should be removed from the code // * in a future release. // */ // public synchronized Service getInterface() { // // return this; // // } /** * {@inheritDoc} */ public Advertisement getImplAdvertisement() { return implAdvertisement; } /** * {@inheritDoc} */ public int getRemoteAdvertisements(String peer, int type, String attribute, String value, int threshold) { return getRemoteAdvertisements(peer, type, attribute, value, threshold, null); } /** * {@inheritDoc} */ public int getRemoteAdvertisements(String peer, int type, String attribute, String value, int threshold, DiscoveryListener listener) { int myQueryID = qid.incrementAndGet(); if (localonly || stopped) { Logging.logCheckedDebug(LOG, "localonly, no network operations performed"); return myQueryID; } if (resolver == null) { // warn about calling the service before it started Logging.logCheckedWarning(LOG, "resolver has not started yet, query discarded."); return myQueryID; } if (Logging.SHOW_DEBUG && LOG.isDebugEnabled()) { StringBuilder query = new StringBuilder("Sending query#" + myQueryID + " for " + threshold + " " + dirname[type] + " advs"); if (attribute != null) { query.append("\n\tattr = ").append(attribute); if (value != null) { query.append("\tvalue = ").append(value); } } LOG.debug(query.toString()); } long t0 = System.currentTimeMillis(); DiscoveryQueryMsg dquery = new DiscoveryQuery(); dquery.setDiscoveryType(type); dquery.setAttr(attribute); dquery.setValue(value); dquery.setThreshold(threshold); if (listener != null) { synchronized (queryListeners) { queryListeners.put(myQueryID, listener); } } ResolverQueryMsg query = new ResolverQuery(); query.setHandlerName(handlerName); CurrentCredential current = currentCredential; if (null != current) { query.setCredential(current.credentialDoc); } query.setSrcPeer(localPeerId); query.setQuery(dquery.getDocument(MimeMediaType.XMLUTF8).toString()); query.setQueryId(myQueryID); // check srdiManager if (peer == null && srdiIndex != null) { List<PeerID> res = srdiIndex.query(dirname[type], attribute, value, threshold); if (!res.isEmpty()) { srdiManager.forwardQuery(res, query, threshold); Logging.logCheckedDebug(LOG, "Srdi forward a query #", myQueryID, " in ", (System.currentTimeMillis() - t0), "ms."); return myQueryID; // nothing in srdiManager, get a starting point in rpv } else if (group.isRendezvous() && attribute != null && value != null) { PeerID destPeer = srdiManager.getReplicaPeer(dirname[type] + attribute + value); if (destPeer != null) { if (!destPeer.equals(localPeerId)) { // forward query increments the hopcount to indicate getReplica // has been invoked once srdiManager.forwardQuery(destPeer, query); Logging.logCheckedDebug(LOG, "Srdi forward query #", myQueryID, " to ", destPeer, " in ", (System.currentTimeMillis() - t0), "ms."); return myQueryID; } } } } // no srdiManager, not a rendezvous, start the walk resolver.sendQuery(peer, query); if (peer == null) { Logging.logCheckedDebug(LOG, "Sent a query #", myQueryID, " in ", (System.currentTimeMillis() - t0), "ms."); } else { Logging.logCheckedDebug(LOG, "Sent a query #", myQueryID, " to ", peer, " in ", (System.currentTimeMillis() - t0), "ms."); } return myQueryID; } /** * {@inheritDoc} */ public Enumeration<Advertisement> getLocalAdvertisements(int type, String attribute, String value) throws IOException { if ((type > DiscoveryService.ADV) || (type < DiscoveryService.PEER)) { throw new IllegalArgumentException("Unknown Advertisement type"); } if (Logging.SHOW_DEBUG && LOG.isDebugEnabled()) { StringBuilder query = new StringBuilder("Searching for " + dirname[type] + " advs"); if (attribute != null) query.append("\n\tattr = ").append(attribute); if (value != null) query.append("\tvalue = ").append(value); LOG.debug(query.toString()); } return Collections.enumeration(search(type, attribute, value, Integer.MAX_VALUE, null)); } /** * {@inheritDoc} */ public void init(PeerGroup pg, ID assignedID, Advertisement impl) throws PeerGroupException { group = pg; handlerName = assignedID.toString(); implAdvertisement = (ModuleImplAdvertisement) impl; localPeerId = group.getPeerID(); ConfigParams confAdv = pg.getConfigAdvertisement(); // Get the config. If we do not have a config, we're done; we just keep // the defaults (edge peer/no auto-rdv) if (confAdv != null) { Advertisement adv = null; try { XMLDocument configDoc = (XMLDocument) confAdv.getServiceParam(assignedID); if (null != configDoc) { adv = AdvertisementFactory.newAdvertisement(configDoc); } } catch (NoSuchElementException failed) { // ignored } if (adv instanceof DiscoveryConfigAdv) { DiscoveryConfigAdv discoConfigAdv = (DiscoveryConfigAdv) adv; alwaysUseReplicaPeer = discoConfigAdv.getForwardAlwaysReplica(); forwardBelowThreshold = discoConfigAdv.getForwardBelowTreshold(); localonly |= discoConfigAdv.getLocalOnly(); } } cm = ((StdPeerGroup) group).getCacheManager(); cm.setTrackDeltas(!localonly); if (Logging.SHOW_CONFIG && LOG.isConfigEnabled()) { StringBuilder configInfo = new StringBuilder("Configuring Discovery Service : " + assignedID); if (implAdvertisement != null) { configInfo.append("\n\tImplementation :"); configInfo.append("\n\t\tModule Spec ID: ").append(implAdvertisement.getModuleSpecID()); configInfo.append("\n\t\tImpl Description : ").append(implAdvertisement.getDescription()); configInfo.append("\n\t\tImpl URI : ").append(implAdvertisement.getUri()); configInfo.append("\n\t\tImpl Code : ").append(implAdvertisement.getCode()); } 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\tLocal Only : ").append(localonly); configInfo.append("\n\t\tAlways Use ReplicaPeer : ").append(alwaysUseReplicaPeer); configInfo.append("\n\t\tForward when below threshold responses : ").append(forwardBelowThreshold); LOG.config(configInfo.toString()); } } /** * {@inheritDoc} */ public int startApp(String[] arg) { resolver = group.getResolverService(); if (null == resolver) { Logging.logCheckedWarning(LOG, "Stalled until there is a resolver service"); return Module.START_AGAIN_STALLED; } membership = group.getMembershipService(); if (null == membership) { Logging.logCheckedWarning(LOG, "Stalled until there is a membership service"); return Module.START_AGAIN_STALLED; } rendezvous = group.getRendezVousService(); if (null == rendezvous) { Logging.logCheckedWarning(LOG, "Stalled until there is a rendezvous service"); return Module.START_AGAIN_STALLED; } // Get the initial credential doc synchronized (this) { membership.addPropertyChangeListener("defaultCredential", membershipCredListener); try { membershipCredListener.propertyChange(new PropertyChangeEvent(membership, "defaultCredential", null, membership.getDefaultCredential())); } catch (Exception all) { Logging.logCheckedWarning(LOG, "Could not get credential\n", all); } } // local only discovery if (!localonly) { resolver.registerHandler(handlerName, this); } if (rendezvous.isRendezVous()) { beRendezvous(); } else { beEdge(); } rendezvous.addListener(this); stopped = false; Logging.logCheckedInfo(LOG, "Discovery service started"); return Module.START_OK; } /** * {@inheritDoc} * <p/> * Detach from the resolver and from rendezvous */ public void stopApp() { stopped = true; boolean failed = false; membership.removePropertyChangeListener("defaultCredential", membershipCredListener); currentCredential = null; rendezvous.removeListener(this); if (resolver.unregisterHandler(handlerName) == null) { failed = true; } if (rendezvous.isRendezVous()) { if (resolver.unregisterSrdiHandler(handlerName) == null) { failed = true; } } if (failed) { Logging.logCheckedWarning(LOG, "failed to unregister discovery from resolver."); } // stop SRDI if (srdiManager != null) srdiManager.stop(); srdiIndex = null; // Forget about all remaining listeners. listeners.clear(); queryListeners.clear(); Logging.logCheckedInfo(LOG, "Discovery service stopped."); } /** * {@inheritDoc} */ public void flushAdvertisements(String id, int type) throws IOException { if (stopped) { return; } if ((type >= PEER) && (type <= ADV)) { if (null != id) { ID advID = ID.create(URI.create(id)); String advName = advID.getUniqueValue().toString(); Logging.logCheckedDebug(LOG, "flushing adv ", advName, " of type ", dirname[type]); cm.remove(dirname[type], advName); } else { // XXX bondolo 20050902 For historical purposes we ignore null Logging.logCheckedWarning(LOG, "Flush request by type IGNORED. You must delete advertisements individually."); } } else { throw new IllegalArgumentException("Invalid Advertisement type."); } } /** * {@inheritDoc} */ public void flushAdvertisement(Advertisement adv) throws IOException { if (stopped) { return; } int type; if (adv instanceof PeerAdvertisement) { type = PEER; } else if (adv instanceof PeerGroupAdvertisement) { type = GROUP; } else { type = ADV; } ID id = adv.getID(); String advName; if (id != null && !id.equals(ID.nullID)) { advName = id.getUniqueValue().toString(); Logging.logCheckedDebug(LOG, "Flushing adv ", advName, " of type ", dirname[type]); } else { XMLDocument doc; try { doc = (XMLDocument) adv.getSignedDocument(); } catch (Exception everything) { IOException failure = new IOException("Failure removing Advertisement"); failure.initCause(everything); throw failure; } advName = CacheManager.createTmpName(doc); } if (advName != null) { cm.remove(dirname[type], advName); } } /** * {@inheritDoc} */ public void publish(Advertisement adv) throws IOException { publish(adv, DiscoveryService.DEFAULT_LIFETIME, DiscoveryService.DEFAULT_EXPIRATION); } /** * {@inheritDoc} */ public void publish(Advertisement adv, long lifetime, long expiration) throws IOException { if (stopped) { return; } ID advID; String advName; int type; if (adv instanceof PeerAdvertisement) { type = PEER; } else if (adv instanceof PeerGroupAdvertisement) { type = GROUP; } else { type = ADV; } advID = adv.getID(); // if we dont have a unique id for the adv, use the hash method if ((null == advID) || advID.equals(ID.nullID)) { XMLDocument doc; try { doc = (XMLDocument) adv.getSignedDocument(); } catch (Exception everything) { Logging.logCheckedWarning(LOG, "Failed to generated document from advertisement\n", everything); IOException failure = new IOException("Failed to generate document from advertisement"); failure.initCause(everything); throw failure; } try { advName = CacheManager.createTmpName(doc); } catch (IllegalStateException ise) { IOException failure = new IOException("Failed to generate tempname from advertisement"); failure.initCause(ise); throw failure; } } else { advName = advID.getUniqueValue().toString(); } Logging.logCheckedDebug(LOG, "Publishing a ", adv.getAdvType(), " as ", dirname[type], " / ", advName, "\n\texpiration : ", expiration, "\tlifetime :", lifetime); // save it cm.save(dirname[type], advName, adv, lifetime, expiration); } /** * {@inheritDoc} */ public void remotePublish(Advertisement adv) { remotePublish(null, adv, DiscoveryService.DEFAULT_EXPIRATION); } /** * {@inheritDoc} */ public void remotePublish(Advertisement adv, long expiration) { remotePublish(null, adv, expiration); } /** * {@inheritDoc} */ public void remotePublish(String peerid, Advertisement adv) { remotePublish(peerid, adv, DiscoveryService.DEFAULT_EXPIRATION); } /** * {@inheritDoc} */ public void processResponse(ResolverResponseMsg response) { processResponse(response, null); } /** * {@inheritDoc} */ public void processResponse(ResolverResponseMsg response, EndpointAddress srcAddress) { if (stopped) { return; } long t0 = System.currentTimeMillis(); DiscoveryResponse res; try { XMLDocument asDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument( MimeMediaType.XMLUTF8, new StringReader(response.getResponse())); res = new DiscoveryResponse(asDoc); } catch (Exception e) { // we don't understand this msg, let's skip it Logging.logCheckedWarning(LOG, "Failed to Read Discovery Response\n", e); return; } /* PeerAdvertisement padv = res.getPeerAdvertisement(); if (padv == null) return; if (LOG.isLoggable(Level.FINE)) { LOG.fine("Got a " + dirname[res.getDiscoveryType()] + " from "+padv.getName()+ " response : " + res.getQueryAttr() + " = " + res.getQueryValue()); } try { // The sender does not put an expiration on that one, but // we do not want to keep it around for more than the // default duration. It may get updated or become invalid. publish(padv, PEER, DEFAULT_EXPIRATION, DEFAULT_EXPIRATION); } catch (Exception e) { if (LOG.isLoggable(Level.FINE)) { LOG.fine(e, e); } return; } */ Advertisement adv; Logging.logCheckedDebug(LOG, "Processing responses for query #", response.getQueryId()); Enumeration<Advertisement> en = res.getAdvertisements(); Enumeration<Long> exps = res.getExpirations(); while (en.hasMoreElements()) { adv = en.nextElement(); long exp = exps.nextElement(); if (exp > 0 && adv != null) { try { publish(adv, exp, exp); } catch (Exception e) { Logging.logCheckedWarning(LOG, "Error publishing Advertisement\n", e); } } } // Generate an event and callback the query listener (if any). DiscoveryEvent newevent = new DiscoveryEvent(srcAddress, res, response.getQueryId()); DiscoveryListener dl; synchronized (queryListeners) { dl = queryListeners.get(response.getQueryId()); } if (dl != null) { try { dl.discoveryEvent(new DiscoveryEvent(srcAddress, res, response.getQueryId())); } catch (Throwable all) { LOG.error("Uncaught Throwable in listener :" + Thread.currentThread().getName(), all); } } Logging.logCheckedDebug(LOG, "processed a response for query #", response.getQueryId(), " in :", (System.currentTimeMillis() - t0)); // Callback any registered discovery listeners. t0 = System.currentTimeMillis(); Collection<DiscoveryListener> allListeners; synchronized (listeners) { allListeners = new ArrayList<DiscoveryListener>(listeners); } for (DiscoveryListener aListener : allListeners) { try { aListener.discoveryEvent(newevent); } catch (Throwable all) { Logging.logCheckedWarning(LOG, "Uncaught Throwable in listener (", aListener.getClass().getName(), ") :", Thread.currentThread().getName(), "\n", all); } } Logging.logCheckedDebug(LOG, "Called all listeners to query #", response.getQueryId(), " in :", (System.currentTimeMillis() - t0)); } /** * {@inheritDoc} */ public int processQuery(ResolverQueryMsg query) { return processQuery(query, null); } /** * {@inheritDoc} */ public int processQuery(ResolverQueryMsg query, EndpointAddress srcAddress) { if (stopped) return ResolverService.OK; if (srcAddress != null) { Logging.logCheckedDebug(LOG, "Processing query #", query.getQueryId(), " from:", srcAddress); } else { Logging.logCheckedDebug(LOG, "Processing query #", query.getQueryId(), " from: unknown"); } DiscoveryQuery dq; long t0 = System.currentTimeMillis(); try { XMLDocument asDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, new StringReader(query.getQuery())); dq = new DiscoveryQuery(asDoc); } catch (Exception e) { Logging.logCheckedWarning(LOG, "Malformed query : \n", e); return ResolverService.OK; } if ((dq.getThreshold() < 0) || (dq.getDiscoveryType() < PEER) || (dq.getDiscoveryType() > ADV)) { Logging.logCheckedWarning(LOG, "Malformed query"); return ResolverService.OK; } Logging.logCheckedDebug(LOG, "Got a ", dirname[dq.getDiscoveryType()], " query #", query.getQueryId(), " query :", dq.getAttr(), " = ", dq.getValue()); /* // Get the Peer Adv from the query and publish it. PeerAdvertisement padv = dq.getPeerAdvertisement(); try { if (!(padv.getPeerID().toString()).equals(localPeerId)) { // publish others only. Since this one comes from outside, // we must not keep it beyond its expiration time. // FIXME: [jice@jxta.org 20011112] In theory there should // be an expiration time associated with it in the msg, like // all other items. publish(padv, PEER, DEFAULT_EXPIRATION, DEFAULT_EXPIRATION); } } catch (Exception e) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("Bad Peer Adv in Discovery Query\n", e); } } */ int thresh = Math.min(dq.getThreshold(), MAX_RESPONSES); /* * threshold==0 and type==PEER is a special case. In this case we are * responding for the purpose of providing our own adv only. */ if ((dq.getDiscoveryType() == PEER) && (0 == dq.getThreshold())) { respond(query, dq, Collections.singletonList(group.getPeerAdvertisement().toString()), Collections.singletonList(DiscoveryService.DEFAULT_EXPIRATION)); Logging.logCheckedDebug(LOG, "Responding to query #", query.getQueryId(), " in :", (System.currentTimeMillis() - t0)); return ResolverService.OK; } Logging.logCheckedDebug(LOG, "start local search query", dq.getAttr(), " ", dq.getValue()); List<Long> expirations = new ArrayList<Long>(); List<InputStream> results = rawSearch(dq.getDiscoveryType(), dq.getAttr(), dq.getValue(), thresh, expirations); if (!results.isEmpty()) { Logging.logCheckedDebug(LOG, "Responding to ", dirname[dq.getDiscoveryType()], " Query : ", dq.getAttr(), " = ", dq.getValue()); respond(query, dq, results, expirations); Logging.logCheckedDebug(LOG, "Responded to query #", query.getQueryId(), " in :", (System.currentTimeMillis() - t0)); } // If this peer is not a rendezvous, just discard the query. if (!group.isRendezvous()) { return ResolverService.OK; } PeerID replicaPeer = srdiManager.getReplicaPeer(dirname[dq.getDiscoveryType()] + dq.getAttr() + dq.getValue()); if ((null != replicaPeer) && !localPeerId.equals(replicaPeer)) { if (alwaysUseReplicaPeer || (forwardBelowThreshold && (results.size() < dq.getThreshold()))) { Logging.logCheckedDebug(LOG, "Forwarding query #", query.getQueryId(), " to replica peer ", replicaPeer); // forward to SRDI replica. srdiManager.forwardQuery(replicaPeer, query); } // In either case we are done. return ResolverService.OK; } // We didn't have sufficient local results or there is no known replica. // See if there are any in SRDI. if (results.isEmpty() || (forwardBelowThreshold && (results.size() < dq.getThreshold()))) { Logging.logCheckedDebug(LOG, "Querying SrdiIndex for query #", query.getQueryId()); List<PeerID> res = srdiIndex.query(dirname[dq.getDiscoveryType()], dq.getAttr(), dq.getValue(), thresh); if (!res.isEmpty()) { srdiManager.forwardQuery(res, query, thresh); } else { // start the walk since this peer is this the starting peer query.incrementHopCount(); return ResolverService.Repropagate; } } return ResolverService.OK; } /** * @param query The resolver query we are responding to. * @param dq The discovery query we are responding to. * @param results The results we are responding with(Advertisemets,Strings,InputStreams). * @param expirations Expiration values for the results. */ private void respond(ResolverQueryMsg query, DiscoveryQuery dq, List results, List<Long> expirations) { if (localonly || stopped) { return; } ResolverResponseMsg response; DiscoveryResponse dresponse = new DiscoveryResponse(); // peer adv is optional, skip dresponse.setDiscoveryType(dq.getDiscoveryType()); dresponse.setQueryAttr(dq.getAttr()); dresponse.setQueryValue(dq.getValue()); dresponse.setResponses(results); dresponse.setExpirations(expirations); // create a response from the query response = query.makeResponse(); CurrentCredential current = currentCredential; if (null != current) { response.setCredential(current.credentialDoc); } response.setResponse(dresponse.toString()); Logging.logCheckedDebug(LOG, "Responding to query #", query.getQueryId(), " ", query.getSrcPeer()); resolver.sendResponse(query.getSrcPeer().toString(), response); } /** * {@inheritDoc} */ public void addDiscoveryListener(DiscoveryListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * {@inheritDoc} */ public boolean removeDiscoveryListener(DiscoveryListener listener) { boolean removed = false; synchronized (queryListeners) { Iterator<DiscoveryListener> eachDiscoveryListener = queryListeners.values().iterator(); while (eachDiscoveryListener.hasNext()) { if (listener == eachDiscoveryListener.next()) { eachDiscoveryListener.remove(); removed = true; } } } synchronized (listeners) { removed |= listeners.remove(listener); } return removed; } /** * {@inheritDoc} */ public void remotePublish(String peerid, Advertisement adv, long timeout) { if (localonly || stopped) { Logging.logCheckedDebug(LOG, "localonly, no network operations performed"); return; } int type; if (adv instanceof PeerAdvertisement) { type = PEER; } else if (adv instanceof PeerGroupAdvertisement) { type = GROUP; } else { type = ADV; } remotePublish(peerid, adv, type, timeout); } /* * remote publish the advertisement */ private void remotePublish(String peerid, Advertisement adv, int type, long expiration) { if (localonly || stopped) { Logging.logCheckedDebug(LOG, "localonly, no network operations performed"); return; } // In case this is invoked before startApp(). if (resolver == null) return; switch (type) { case PEER: if (adv instanceof PeerAdvertisement) break; throw new IllegalArgumentException("Not a peer advertisement"); case GROUP: if (adv instanceof PeerGroupAdvertisement) break; throw new IllegalArgumentException("Not a peergroup advertisement"); case ADV: break; default: throw new IllegalArgumentException("Unknown advertisement type"); } DiscoveryResponseMsg dresponse = new DiscoveryResponse(); dresponse.setDiscoveryType(type); dresponse.setResponses(Collections.singletonList(adv.toString())); dresponse.setExpirations(Collections.singletonList(expiration)); ResolverResponseMsg pushRes = new ResolverResponse(); pushRes.setHandlerName(handlerName); CurrentCredential current = currentCredential; if (null != current) { pushRes.setCredential(current.credentialDoc); } pushRes.setQueryId(REMOTE_PUBLISH_QUERYID); pushRes.setResponse(dresponse.toString()); Logging.logCheckedDebug(LOG, "Remote publishing"); resolver.sendResponse(peerid, pushRes); } /** * Search for Advertisements that matches attr and value. * * @param type Discovery type PEER, GROUP, ADV * @param threshold the upper limit of responses from one peer * @param expirations List containing the expirations associated with is returned * @param attr attribute name to narrow discovery to Valid values for * this parameter are null (don't care), or exact element name in the * advertisement of interest (e.g. "Name") * @param value Value * @return list of results either as docs, or Strings */ private List<InputStream> rawSearch(int type, String attr, String value, int threshold, List<Long> expirations) { if (stopped) { return new ArrayList(); } if (type == PEER) { checkUpdatePeerAdv(); } List<InputStream> results; if (threshold <= 0) { throw new IllegalArgumentException("threshold must be greater than zero"); } if (expirations != null) expirations.clear(); if (attr != null) { Logging.logCheckedDebug(LOG, "Searching for ", threshold, " entries of type : ", dirname[type]); // a discovery query with a specific search criteria. results = cm.search(dirname[type], attr, value, threshold, expirations); } else { Logging.logCheckedDebug(LOG, "Getting ", threshold, " entries of type : ", dirname[type]); // Returning any entry that exists results = cm.getRecords(dirname[type], threshold, expirations); } Logging.logCheckedDebug(LOG, "Returning ", results.size(), " results"); // nothing more to do; return results; } /** * Search for Advertisements that matches attr and value. * * @param type Discovery type PEER, GROUP, ADV * @param threshold the upper limit of responses from one peer * @param expirations List containing the expirations associated with is returned * @param attr attribute name to narrow discovery to Valid values for * this parameter are null (don't care), or exact element name in the * advertisement of interest (e.g. "Name") * @param value Value * @return list of results either as docs, or Strings */ private List<Advertisement> search(int type, String attr, String value, int threshold, List<Long> expirations) { List<InputStream> results = rawSearch(type, attr, value, threshold, expirations); // Convert the input streams returned by the cm into Advertisements. List<Advertisement> advertisements = new ArrayList<Advertisement>(); for (int i = 0; (i < results.size()) && (advertisements.size() < threshold); i++) { try { InputStream bis = results.get(i); XMLDocument asDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, bis); Advertisement adv = AdvertisementFactory.newAdvertisement(asDoc); advertisements.add(adv); } catch (Exception e) { Logging.logCheckedWarning(LOG, "Failed building advertisment\n", e); // we won't be including this advertisement so remove it's expiration. if (null != expirations) expirations.remove(i); } } Logging.logCheckedDebug(LOG, "Returning ", advertisements.size(), " advertisements"); return advertisements; } /** * {@inheritDoc} */ public long getAdvExpirationTime(ID id, int type) { if (stopped) return -1; String advName; if (id != null && !id.equals(ID.nullID)) { advName = id.getUniqueValue().toString(); Logging.logCheckedDebug(LOG, "Getting expiration time of ", advName, " of type ", dirname[type]); } else { Logging.logCheckedDebug(LOG, "invalid attempt to get advertisement expiration time of NullID"); return -1; } return cm.getExpirationtime(dirname[type], advName); } /** * {@inheritDoc} */ public long getAdvLifeTime(ID id, int type) { if (id == null || id.equals(ID.nullID) || stopped) { Logging.logCheckedWarning(LOG, "invalid attempt to get advertisement lifetime of a NullID"); return -1; } String advName = id.getUniqueValue().toString(); Logging.logCheckedDebug(LOG, "Getting lifetime of ", advName, " of type ", dirname[type]); return cm.getLifetime(dirname[type], advName); } /** * {@inheritDoc} */ public long getAdvExpirationTime(Advertisement adv) { if (stopped) { return -1; } int type; if (adv instanceof PeerAdvertisement) { type = PEER; } else if (adv instanceof PeerGroupAdvertisement) { type = GROUP; } else { type = ADV; } String advName; ID id = adv.getID(); if (id != null && !id.equals(ID.nullID)) { advName = id.getUniqueValue().toString(); Logging.logCheckedDebug(LOG, "attempting to getAdvExpirationTime on ", advName, " of type ", dirname[type]); } else { XMLDocument doc; try { doc = (XMLDocument) adv.getSignedDocument(); } catch (Exception everything) { Logging.logCheckedWarning(LOG, "Failed to get document\n", everything); return -1; } advName = CacheManager.createTmpName(doc); } return cm.getExpirationtime(dirname[type], advName); } /** * {@inheritDoc} */ public long getAdvLifeTime(Advertisement adv) { if (stopped) { return -1; } int type; if (adv instanceof PeerAdvertisement) { type = PEER; } else if (adv instanceof PeerGroupAdvertisement) { type = GROUP; } else { type = ADV; } ID id = adv.getID(); String advName; if (id != null && !id.equals(ID.nullID)) { advName = id.getUniqueValue().toString(); Logging.logCheckedDebug(LOG, "attempting to getAdvLifeTime ", advName, " of type ", dirname[type]); } else { XMLDocument doc; try { doc = (XMLDocument) adv.getSignedDocument(); } catch (Exception everything) { Logging.logCheckedWarning(LOG, "Failed to get document\n", everything); return -1; } advName = CacheManager.createTmpName(doc); } return cm.getLifetime(dirname[type], advName); } /** * {@inheritDoc} */ public boolean processSrdi(ResolverSrdiMsg message) { if (stopped) return true; Logging.logCheckedDebug(LOG, "[", group.getPeerGroupID(), "] Received an SRDI messsage"); SrdiMessage srdiMsg; try { XMLDocument asDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, new StringReader(message.getPayload())); srdiMsg = new SrdiMessageImpl(asDoc); } catch (Exception e) { Logging.logCheckedWarning(LOG, "Failed parsing srdi message\n", e); return false; } PeerID pid = srdiMsg.getPeerID(); for (Object o : srdiMsg.getEntries()) { SrdiMessage.Entry entry = (SrdiMessage.Entry) o; srdiIndex.add(srdiMsg.getPrimaryKey(), entry.key, entry.value, pid, entry.expiration); Logging.logCheckedDebug(LOG, "Primary Key [", srdiMsg.getPrimaryKey(), "] key [", entry.key, "] value [", entry.value, "] exp [", entry.expiration, "]"); } srdiManager.replicateEntries(srdiMsg); return true; } /** * {@inheritDoc} */ public void messageSendFailed(PeerID peerid, OutgoingMessageEvent e) { if (srdiIndex != null) { srdiIndex.remove(peerid); } } /** * {@inheritDoc} */ public void pushEntries(boolean all) { pushSrdi(null, PEER, all); pushSrdi(null, GROUP, all); pushSrdi(null, ADV, all); } /** * push srdiManager entries * * @param all if true push all entries, otherwise just deltas * @param peer peer id * @param type if true sends all entries */ protected void pushSrdi(ID peer, int type, boolean all) { if (stopped) { return; } List<SrdiMessage.Entry> entries; if (all) { entries = cm.getEntries(dirname[type], true); } else { entries = cm.getDeltas(dirname[type]); } if (!entries.isEmpty()) { SrdiMessage srdiMsg; try { srdiMsg = new SrdiMessageImpl(localPeerId, 1, // ttl of 1, ensure it is replicated dirname[type], entries); // LOGGING: was Finer Logging.logCheckedDebug(LOG, "Pushing ", entries.size(), (all ? " entries" : " deltas"), " of type ", dirname[type]); srdiManager.pushSrdi(peer, srdiMsg); } catch (Exception e) { Logging.logCheckedWarning(LOG, "Exception pushing SRDI Entries\n", e); } } else { // LOGGING: was Finer Logging.logCheckedDebug(LOG, "No", (all ? " entries" : " deltas"), " of type ", dirname[type], " to push"); } } /** * {@inheritDoc} */ public synchronized void rendezvousEvent(RendezvousEvent event) { int theEventType = event.getType(); Logging.logCheckedDebug(LOG, "[", group.getPeerGroupName(), "] Processing ", event); switch (theEventType) { case RendezvousEvent.RDVCONNECT: case RendezvousEvent.RDVRECONNECT: // start tracking deltas cm.setTrackDeltas(true); break; case RendezvousEvent.CLIENTCONNECT: case RendezvousEvent.CLIENTRECONNECT: break; case RendezvousEvent.RDVFAILED: case RendezvousEvent.RDVDISCONNECT: // stop tracking deltas until we connect again cm.setTrackDeltas(false); break; case RendezvousEvent.CLIENTFAILED: case RendezvousEvent.CLIENTDISCONNECT: break; case RendezvousEvent.BECAMERDV: beRendezvous(); break; case RendezvousEvent.BECAMEEDGE: beEdge(); break; default: Logging.logCheckedWarning(LOG, MessageFormat.format("[{0}] Unexpected RDV event : {1}", group.getPeerGroupName(), event)); break; } } /** * Checks to see if the local peer advertisement has been updated and if * it has then republish it to the CM. */ private void checkUpdatePeerAdv() { PeerAdvertisement newPadv = group.getPeerAdvertisement(); int newModCount = newPadv.getModCount(); boolean updated = false; synchronized (checkPeerAdvLock) { if ((lastPeerAdv != newPadv) || (lastModCount < newModCount)) { lastPeerAdv = newPadv; lastModCount = newModCount; updated = true; } } if (updated) { // Publish the local Peer Advertisement try { Logging.logCheckedDebug(LOG, "publishing local advertisement"); // This is our own; we can publish it for a long time in our cache publish(newPadv, INFINITE_LIFETIME, DEFAULT_EXPIRATION); } catch (Exception ignoring) { Logging.logCheckedWarning(LOG, "Could not publish local peer advertisement: \n", ignoring); } } } /** * Change the behavior to be an rendezvous Peer Discovery Service. * If the Service was acting as an Edge peer, cleanup. */ private synchronized void beRendezvous() { if (isRdv && (srdiManager != null || srdiIndex != null)) { Logging.logCheckedInfo(LOG, "Already a rendezvous -- No Switch is needed"); return; } isRdv = true; // rdv peers do not need to track deltas cm.setTrackDeltas(false); if (srdiIndex == null) { srdiIndex = new Srdi(group, srdiIndexerFileName); Logging.logCheckedDebug(LOG, "srdiIndex created"); } // Kill SRDI, create a new one. if (srdiManager != null) { srdiManager.stop(); srdiManager = null; } if (!localonly) { srdiManager = new SrdiManager(group, handlerName, this, srdiIndex); resolver.registerSrdiHandler(handlerName, this); Logging.logCheckedDebug(LOG, "srdi created, and registered as an srdi handler "); } Logging.logCheckedInfo(LOG, "Switched to Rendezvous peer role."); } /** * Change the behavior to be an Edge Peer Discovery Service. * If the Service was acting as a Rendezvous, cleanup. */ private synchronized void beEdge() { // make sure we have been here before if (!isRdv) { Logging.logCheckedInfo(LOG, "Already an Edge peer -- No Switch is needed."); return; } isRdv = false; if (rendezvous.isConnectedToRendezVous()) { // if we have a rendezvous connection track deltas, otherwise wait // for a connect event to set this option cm.setTrackDeltas(true); } if (srdiIndex != null) { srdiIndex.stop(); srdiIndex = null; resolver.unregisterSrdiHandler(handlerName); Logging.logCheckedDebug(LOG, "stopped cache and unregistered from resolver"); } // Kill SRDI if (srdiManager != null) { srdiManager.stop(); srdiManager = null; } if (!localonly) { // Create a new SRDI manager srdiManager = new SrdiManager(group, handlerName, this, null); srdiManager.startPush(group.getTaskManager().getScheduledExecutorService(), runInterval); } Logging.logCheckedInfo(LOG, "Switched to a Edge peer role."); } }