/* * 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.document.Advertisement; import net.jxta.id.ID; import net.jxta.id.IDFactory; import net.jxta.impl.util.TimeUtils; import net.jxta.logging.Logging; import net.jxta.peer.PeerID; import net.jxta.peergroup.PeerGroup; import net.jxta.pipe.InputPipe; import net.jxta.pipe.OutputPipe; import net.jxta.pipe.OutputPipeEvent; import net.jxta.pipe.OutputPipeListener; import net.jxta.pipe.PipeID; import net.jxta.pipe.PipeMsgListener; import net.jxta.pipe.PipeService; import net.jxta.platform.Module; import net.jxta.protocol.ModuleImplAdvertisement; import net.jxta.protocol.PipeAdvertisement; import net.jxta.service.Service; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * A JXTA {@link net.jxta.pipe.PipeService} implementation which implements the * standard JXTA Pipe Resolver Protocol (PRP). * <p/> * This class provides implementation for Unicast, unicast secure and * (indirectly) propagate pipes. * * @see net.jxta.pipe.PipeService * @see net.jxta.pipe.InputPipe * @see net.jxta.pipe.OutputPipe * @see net.jxta.endpoint.Message * @see net.jxta.protocol.PipeAdvertisement * @see net.jxta.protocol.PipeResolverMessage * @see <a * href="https://jxta-spec.dev.java.net/nonav/JXTAProtocols.html#proto-pbp" * target="_blank">JXTA Protocols Specification : Pipe Binding Protocol</a> */ public class PipeServiceImpl implements PipeService, PipeResolver.Listener { /** * The Logger */ private final static Logger LOG = Logger.getLogger(PipeServiceImpl.class .getName()); /** * the interval at which we verify that a pipe is still resolved at a remote * peer. */ static final long VERIFYINTERVAL = 20 * TimeUtils.AMINUTE; /** * The group this PipeService is working for. */ private PeerGroup group = null; /** * Our resolver handler. */ private PipeResolver pipeResolver = null; /** * Link to wire pipe impl. */ private WirePipeImpl wirePipe = null; /** * the interface object we will hand out. */ private PipeService myInterface = null; /** * the impl advertisement for this impl. */ private ModuleImplAdvertisement implAdvertisement = null; /** * Table of listeners for asynchronous output pipe creation. * <p/> * <ul> * <li>keys are {@link net.jxta.pipe.PipeID}</li> * <li>values are {@link java.util.Map}</li> * </ul> * Within the value Map: * <ul> * <li>keys are {@link java.lang.Integer} representing queryid</li> * <li>values are {@link OutputPipeHolder}</li> * </ul> */ private final Map<PipeID, Map<Integer, OutputPipeHolder>> outputPipeListeners = new HashMap<PipeID, Map<Integer, OutputPipeHolder>>(); /** * Has the pipe service been started? */ private volatile boolean started = false; /** * holds a pipe adv and a listener which will be called for resolutions of * the pipe. */ private static class OutputPipeHolder { final PipeAdvertisement adv; final Set<? extends ID> peers; final OutputPipeListener listener; final int queryid; OutputPipeHolder(PipeAdvertisement adv, Set<? extends ID> peers, OutputPipeListener listener, int queryid) { this.adv = adv; this.peers = peers; this.listener = listener; this.queryid = queryid; } } /** * A listener useful for implementing synchronous behaviour. */ private static class syncListener implements OutputPipeListener { volatile OutputPipeEvent event = null; syncListener() { } /** * Called when a input pipe has been located for a previously registered * pipe. The event contains an {@link net.jxta.pipe.OutputPipe} which * can be used to communicate with the remote peer. * * @param event * <code>net.jxta.pipe.outputPipeEvent</code> event */ public synchronized void outputPipeEvent(OutputPipeEvent event) { // we only accept the first event. if (null == this.event) { this.event = event; notifyAll(); } } } /** * Default Constructor (don't delete) */ public PipeServiceImpl() {// What is reason for this constructor??? // the same is automatically generated. } /** * {@inheritDoc} * <p/> * We create only a single interface object and return it over and over * again. */ public synchronized PipeService getInterface() { if (null == myInterface) { myInterface = new PipeServiceInterface(this); } return myInterface; } /** * {@inheritDoc} */ public ModuleImplAdvertisement getImplAdvertisement() { return implAdvertisement; } /** * {@inheritDoc} */ public synchronized void init(PeerGroup group, ID assignedID, Advertisement impl) { this.group = group; implAdvertisement = (ModuleImplAdvertisement) impl; if (Logging.SHOW_CONFIG && LOG.isLoggable(Level.CONFIG)) { StringBuilder configInfo = new StringBuilder( "Configuring Pipe 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); configInfo.append("\n\t\tPeer ID : ").append(group.getPeerID()); configInfo.append("\n\tConfiguration :"); configInfo.append("\n\t\tVerify Interval : " + VERIFYINTERVAL + "ms"); LOG.config(configInfo.toString()); } } /** * {@inheritDoc} * <p/> * Currently this service does not expect arguments. */ public synchronized int startApp(String[] args) { Service needed = group.getEndpointService(); if (null == needed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Stalled until there is an endpoint service"); } return START_AGAIN_STALLED; } needed = group.getResolverService(); if (null == needed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Stalled until there is a resolver service"); } return START_AGAIN_STALLED; } needed = group.getMembershipService(); if (null == needed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Stalled until there is a membership service"); } return START_AGAIN_STALLED; } needed = group.getRendezVousService(); if (null == needed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Stalled until there is a rendezvous service"); } return START_AGAIN_STALLED; } // create our resolver handler; it will register itself w/ the resolver. pipeResolver = new PipeResolver(group); // Create the WirePipe (propagated pipe) wirePipe = new WirePipeImpl(group, pipeResolver); // XXX 20061221 We could check the result of this. wirePipe.startApp(args); started = true; return Module.START_OK; } /** * {@inheritDoc} */ public synchronized void stopApp() { started = false; try { if (wirePipe != null) { wirePipe.stopApp(); } } catch (Throwable failed) { LOG.log(Level.SEVERE, "Failed to stop wire pipe", failed); } finally { wirePipe = null; } try { if (pipeResolver != null) { pipeResolver.stop(); } } catch (Throwable failed) { LOG.log(Level.SEVERE, "Failed to stop pipe resolver", failed); } finally { pipeResolver = null; } // Avoid cross-reference problem with GC group = null; myInterface = null; // clear outputPipeListeners Collection<Map<Integer, OutputPipeHolder>> values = outputPipeListeners .values(); for (Map<Integer, OutputPipeHolder> value : values) { value.clear(); } outputPipeListeners.clear(); } /** * {@inheritDoc} */ public InputPipe createInputPipe(PipeAdvertisement adv) throws IOException { return createInputPipe(adv, null); } /** * {@inheritDoc} */ public InputPipe createInputPipe(PipeAdvertisement adv, PipeMsgListener listener) throws IOException { if (!started) { throw new IllegalStateException( "Pipe Service has not been started or has been stopped"); } String type = adv.getType(); if (type == null) { throw new IllegalArgumentException( "PipeAdvertisement type may not be null"); } PipeID pipeId = (PipeID) adv.getPipeID(); if (pipeId == null) { throw new IllegalArgumentException( "PipeAdvertisement PipeID may not be null"); } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Create " + type + " InputPipe for " + pipeId); } InputPipe inputPipe; // create an InputPipe. if (type.equals(PipeService.UnicastType)) { inputPipe = new InputPipeImpl(pipeResolver, adv, listener); } else if (type.equals(PipeService.UnicastSecureType)) { inputPipe = new SecureInputPipeImpl(pipeResolver, adv, listener); } else if (type.equals(PipeService.PropagateType)) { if (wirePipe != null) { inputPipe = wirePipe.createInputPipe(adv, listener); } else { throw new IOException("No propagated pipe servive available"); } } else { // Unknown type if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.severe("Cannot create pipe for unknown type : " + type); } throw new IOException("Cannot create pipe for unknown type : " + type); } return inputPipe; } /** * {@inheritDoc} */ public OutputPipe createOutputPipe(PipeAdvertisement pipeAdv, long timeout) throws IOException { return createOutputPipe(pipeAdv, Collections.<ID> emptySet(), timeout); } /** * {@inheritDoc} */ public OutputPipe createOutputPipe(PipeAdvertisement adv, Set<? extends ID> resolvablePeers, long timeout) throws IOException { // convert zero to max value. if (0 == timeout) { timeout = Long.MAX_VALUE; } long absoluteTimeOut = TimeUtils.toAbsoluteTimeMillis(timeout); // Make a listener, start async resolution and then wait until the // timeout expires. syncListener localListener = new syncListener(); int queryid = PipeResolver.getNextQueryID(); createOutputPipe(adv, resolvablePeers, localListener, queryid); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Waiting synchronously for " + timeout + "ms to resolve OutputPipe for " + adv.getPipeID()); } try { synchronized (localListener) { while ((null == localListener.event) && (TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), absoluteTimeOut) < 0)) { try { localListener.wait(TimeUtils.ASECOND); } catch (InterruptedException woken) { Thread.interrupted(); } } } } finally { // remove the listener we installed. removeOutputPipeListener(adv.getPipeID().toString(), queryid); } if (null != localListener.event) { return localListener.event.getOutputPipe(); } else { throw new IOException("Output Pipe could not be resolved after " + timeout + "ms."); } } /** * {@inheritDoc} */ public void createOutputPipe(PipeAdvertisement pipeAdv, OutputPipeListener listener) throws IOException { createOutputPipe(pipeAdv, Collections.<ID> emptySet(), listener); } /** * {@inheritDoc} */ public void createOutputPipe(PipeAdvertisement pipeAdv, Set<? extends ID> resolvablePeers, OutputPipeListener listener) throws IOException { createOutputPipe(pipeAdv, resolvablePeers, listener, PipeResolver .getNextQueryID()); } private void createOutputPipe(PipeAdvertisement pipeAdv, Set<? extends ID> resolvablePeers, OutputPipeListener listener, int queryid) throws IOException { if (!started) { throw new IOException( "Pipe Service has not been started or has been stopped"); } // Recover the PipeId from the PipeServiceImpl Advertisement PipeID pipeId = (PipeID) pipeAdv.getPipeID(); String type = pipeAdv.getType(); if (null == type) { IllegalArgumentException failed = new IllegalArgumentException( "Pipe type was not set"); if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.log(Level.SEVERE, failed.getMessage(), failed); } throw failed; } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Create " + type + " OutputPipe for " + pipeId); } if (PipeService.PropagateType.equals(type)) { OutputPipe op; if (resolvablePeers.size() == 1) { op = new BlockingWireOutputPipe(group, pipeAdv, (PeerID) resolvablePeers.iterator().next()); } else { if (wirePipe != null) { op = wirePipe.createOutputPipe(pipeAdv, resolvablePeers); } else { throw new IOException( "No propagated pipe service available"); } } if (null != op) { OutputPipeEvent newevent = new OutputPipeEvent(this .getInterface(), op, pipeId.toString(), PipeResolver.ANYQUERY); try { listener.outputPipeEvent(newevent); } catch (Throwable ignored) { if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.log(Level.SEVERE, "Uncaught Throwable in listener for " + pipeId + " (" + listener.getClass().getName() + ")", ignored); } } } } else if (PipeService.UnicastType.equals(type) || PipeService.UnicastSecureType.equals(type)) { addOutputPipeListener(pipeId, new OutputPipeHolder(pipeAdv, resolvablePeers, listener, queryid)); pipeResolver.addListener(pipeId, this, queryid); pipeResolver.sendPipeQuery(pipeAdv, resolvablePeers, queryid); // look locally for the pipe if (resolvablePeers.isEmpty() || resolvablePeers.contains(group.getPeerID())) { InputPipe local = pipeResolver.findLocal(pipeId); // if we have a local instance, make sure the local instance is // of the same type. if (null != local) { if (local.getType().equals(pipeAdv.getType())) { pipeResolver.callListener(queryid, pipeId, local .getType(), group.getPeerID(), false); } else { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG .warning(MessageFormat .format( "rejecting local pipe ({0}) because type is not ({1})", local.getType(), pipeAdv .getType())); } } } } } else { // Unknown type if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG .severe("createOutputPipe: cannot create pipe for unknown type : " + type); } throw new IOException("cannot create pipe for unknown type : " + type); } } /* * Add an output listener for the given pipeId. */ private void addOutputPipeListener(PipeID pipeId, OutputPipeHolder pipeHolder) { synchronized (outputPipeListeners) { Map<Integer, OutputPipeHolder> perpipelisteners = outputPipeListeners .get(pipeId); if (perpipelisteners == null) { perpipelisteners = new HashMap<Integer, OutputPipeHolder>(); outputPipeListeners.put(pipeId, perpipelisteners); } if (perpipelisteners.get(pipeHolder.queryid) != null) { LOG.warning("Clobbering output pipe listener for query " + pipeHolder.queryid); } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Adding pipe listener for pipe " + pipeId + " and query " + pipeHolder.queryid); } perpipelisteners.put(pipeHolder.queryid, pipeHolder); } } /** * {@inheritDoc} */ public OutputPipeListener removeOutputPipeListener(ID pipeID, OutputPipeListener listener) { // remove all instances of this listener, regardless of queryid if (pipeResolver == null) { return null; } if (!(pipeID instanceof PipeID)) { throw new IllegalArgumentException("pipeID must be a PipeID."); } synchronized (outputPipeListeners) { Map<Integer, OutputPipeHolder> perpipelisteners = outputPipeListeners .get(pipeID); if (perpipelisteners != null) { Set<Map.Entry<Integer, OutputPipeHolder>> entries = perpipelisteners .entrySet(); for (Map.Entry<Integer, OutputPipeHolder> entry : entries) { OutputPipeHolder pl = entry.getValue(); if (pl.listener == listener) { pipeResolver .removeListener((PipeID) pipeID, pl.queryid); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Removing listener for query " + pl.queryid); } perpipelisteners.remove(entry.getKey()); } } // clean up the map if there are no more listeners for the pipe if (perpipelisteners.isEmpty()) { outputPipeListeners.remove(pipeID); } } } return listener; } private OutputPipeListener removeOutputPipeListener(String opID, int queryID) { if (pipeResolver == null) { return null; } PipeID pipeID; try { URI aPipeID = new URI(opID); pipeID = (PipeID) IDFactory.fromURI(aPipeID); } catch (URISyntaxException badID) { throw new IllegalArgumentException("Bad pipe ID: " + opID); } catch (ClassCastException badID) { throw new IllegalArgumentException("id was not a pipe id: " + opID); } synchronized (outputPipeListeners) { Map<Integer, OutputPipeHolder> perpipelisteners = outputPipeListeners .get(pipeID); if (perpipelisteners != null) { OutputPipeHolder pipeHolder = perpipelisteners.get(queryID); perpipelisteners.remove(queryID); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Removing listener for query " + queryID); } // clean up the map if there are no more listeners for the pipe if (perpipelisteners.isEmpty()) { outputPipeListeners.remove(pipeID); } pipeResolver.removeListener(pipeID, queryID); if (pipeHolder != null) { return pipeHolder.listener; } } } return null; } /** * {@inheritDoc} */ public boolean pipeResolveEvent(PipeResolver.Event event) { try { ID peerID = event.getPeerID(); ID pipeID = event.getPipeID(); int queryID = event.getQueryID(); OutputPipeHolder pipeHolder; synchronized (outputPipeListeners) { Map<Integer, OutputPipeHolder> perpipelisteners = outputPipeListeners .get(pipeID); if (perpipelisteners == null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("No listener for event for pipe " + pipeID); } return false; } pipeHolder = perpipelisteners.get(queryID); if (pipeHolder == null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("No listener for event for query " + queryID); } return false; } } // check if they wanted a resolve from a specific peer. if (!pipeHolder.peers.isEmpty() && !pipeHolder.peers.contains(peerID)) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Event was for wrong peer \'" + peerID + "\'. Discarding."); } return false; } // create op String type = pipeHolder.adv.getType(); OutputPipe op; if (PipeService.UnicastType.equals(type)) { op = new NonBlockingOutputPipe(group, pipeResolver, pipeHolder.adv, peerID, pipeHolder.peers); } else if (PipeService.UnicastSecureType.equals(type)) { op = new SecureOutputPipe(group, pipeResolver, pipeHolder.adv, peerID, pipeHolder.peers); } else { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Could not create output pipe of type \'" + type + "\'. Discarding."); } return false; } // Generate an event when the output pipe was succesfully opened. OutputPipeEvent newevent = new OutputPipeEvent(this.getInterface(), op, pipeID.toString(), queryID); try { pipeHolder.listener.outputPipeEvent(newevent); } catch (Throwable ignored) { if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.log(Level.SEVERE, "Uncaught Throwable in listener for " + pipeID + "(" + pipeHolder.getClass().getName() + ")", ignored); } } removeOutputPipeListener(pipeID.toString(), queryID); return true; } catch (IOException ie) { if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.log(Level.SEVERE, "Error creating output pipe " + event.getPipeID(), ie); } } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("No listener for event for " + event.getPipeID()); } return false; } /** * {@inheritDoc} * <p/> * We don't do anything with NAKs (yet) */ public boolean pipeNAKEvent(PipeResolver.Event event) { return false; } }