/* * Copyright (c) 2006-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.util; import net.jxta.credential.Credential; import net.jxta.document.AdvertisementFactory; import net.jxta.document.MimeMediaType; import net.jxta.document.StructuredDocument; import net.jxta.document.StructuredDocumentFactory; import net.jxta.document.XMLDocument; import net.jxta.endpoint.EndpointAddress; import net.jxta.endpoint.EndpointService; import net.jxta.endpoint.Message; import net.jxta.endpoint.MessageElement; import net.jxta.endpoint.Messenger; import net.jxta.endpoint.ByteArrayMessageElement; import net.jxta.endpoint.StringMessageElement; import net.jxta.endpoint.TextDocumentMessageElement; import net.jxta.id.ID; import net.jxta.impl.endpoint.tcp.TcpMessenger; import net.jxta.impl.util.pipe.reliable.Defs; import net.jxta.impl.util.pipe.reliable.FixedFlowControl; import net.jxta.impl.util.pipe.reliable.OutgoingMsgrAdaptor; import net.jxta.impl.util.pipe.reliable.ReliableInputStream; import net.jxta.impl.util.pipe.reliable.ReliableOutputStream; 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.OutputPipe; import net.jxta.pipe.OutputPipeEvent; import net.jxta.pipe.OutputPipeListener; import net.jxta.pipe.PipeID; import net.jxta.pipe.PipeMsgEvent; import net.jxta.pipe.PipeMsgListener; import net.jxta.pipe.PipeService; import net.jxta.protocol.PeerAdvertisement; import net.jxta.protocol.PipeAdvertisement; import java.io.IOException; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.SocketTimeoutException; import java.util.Collections; import java.util.Iterator; import java.util.Properties; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * JxtaBiDiPipe is a pair of UnicastPipe channels that implements a bidirectional pipe. * By default, JxtaBiDiPipe operates in reliable mode, unless otherwise specified, * in addition, messages must not exceed the Endpoint MTU size of 64K (to make sure * bandwidth is distributed fairly). This size can be configured in the * {@code AbstractMessenger} class. * <p/> * It highly recommended that an application message listener is specified, not doing so, may * lead to message loss in the event the internal queue is overflowed. * <p/> * Sending messages vis {@link #sendMessage(Message)} from within a * {@code PipeMsgListener} may result in a deadlock due to contention * between the sending and receiving portions of BiDi pipes. * <p/> * JxtaBiDiPipe, whenever possible, will attempt to utilize direct tcp messengers, * which leads to improved performance. */ public class JxtaBiDiPipe implements PipeMsgListener, OutputPipeListener, ReliableInputStream.MsgListener { /** * Logger */ private final static transient Logger LOG = Logger.getLogger(JxtaBiDiPipe.class.getName()); private final static int MAXRETRYTIMEOUT = 120 * 1000; private PipeAdvertisement remotePipeAdv; private PeerAdvertisement remotePeerAdv; protected int timeout = 15 * 1000; protected int retryTimeout = 60 * 1000; protected int maxRetryTimeout = MAXRETRYTIMEOUT; protected int windowSize = 50; private BlockingQueue<PipeMsgEvent> queue = null; protected PeerGroup group; protected PipeAdvertisement pipeAdv; protected PipeAdvertisement myPipeAdv; protected PipeService pipeSvc; protected InputPipe inputPipe; protected OutputPipe connectOutpipe; protected Messenger msgr; protected InputStream stream; protected final Object closeLock = new Object(); protected final Object acceptLock = new Object(); protected final Object finalLock = new Object(); protected boolean closed = false; protected boolean bound = false; protected PipeMsgListener msgListener; protected PipeEventListener eventListener; protected PipeStateListener stateListener; protected Credential credential = null; protected boolean waiting; /** * If {@code true} then we are using the underlying end-to-end ACK reliable * layer to ensure that messages are received by the remote peer. */ protected boolean isReliable = false; protected ReliableInputStream ris = null; protected ReliableOutputStream ros = null; /** * If {@code true} then we are using a reliable direct messenger to the * remote peer. We will assume that messages which are sent successfully * will be received successfully. */ protected volatile boolean direct = false; protected OutgoingMsgrAdaptor outgoing = null; protected StructuredDocument credentialDoc = null; protected Properties connectionProperties = null; /** * Pipe close Event */ public static final int PIPE_CLOSED_EVENT = 1; /** * Creates a bidirectional pipe * * @param group group context * @param msgr lightweight output pipe * @param pipe PipeAdvertisement * @param isReliable Whether the connection is reliable or not * @param credDoc Credential StructuredDocument * @param direct indicates a direct messenger pipe * @throws IOException if an io error occurs */ protected JxtaBiDiPipe(PeerGroup group, Messenger msgr, PipeAdvertisement pipe, StructuredDocument credDoc, boolean isReliable, boolean direct) throws IOException { if (msgr == null) { throw new IOException("Null Messenger"); } this.direct = direct; this.group = group; this.pipeAdv = pipe; this.credentialDoc = credDoc != null ? credDoc : getCredDoc(group); this.pipeSvc = group.getPipeService(); this.inputPipe = pipeSvc.createInputPipe(pipe, this); this.msgr = msgr; this.isReliable = isReliable; if (msgListener == null) { queue = new ArrayBlockingQueue<PipeMsgEvent>(windowSize); } if (!direct) { createRLib(); } setBound(); } /** * Creates a bidirectional pipe * * @param group group context * @param msgr lightweight output pipe * @param pipe PipeAdvertisement * @param isReliable Whether the connection is reliable or not * @param credDoc Credential StructuredDocument * @param direct indicates a direct messenger pipe * @param connectionProperties Properties associated with this connection * @throws IOException if an io error occurs */ protected JxtaBiDiPipe(PeerGroup group, Messenger msgr, PipeAdvertisement pipe, StructuredDocument credDoc, boolean isReliable, boolean direct, Properties connectionProperties) throws IOException { this(group, msgr, pipe, credDoc, isReliable, direct); this.connectionProperties = connectionProperties; } /** * Creates a new object with a default timeout of #timeout, and no reliability. */ public JxtaBiDiPipe() { } /** * Creates a new object with a default timeout of #timeout, and no reliability. */ public JxtaBiDiPipe(Properties properties) { connectionProperties = properties; } /** * Creates a bidirectional pipe. * * Attempts to create a bidirectional connection to remote peer within default * timeout of #timeout. * * @param group group context * @param pipeAd PipeAdvertisement * @param msgListener application PipeMsgListener * @throws IOException if an io error occurs */ public JxtaBiDiPipe(PeerGroup group, PipeAdvertisement pipeAd, PipeMsgListener msgListener) throws IOException { connect(group, null, pipeAd, timeout, msgListener); } /** * Creates a bidirectional pipe. * * Attempts to create a bidirectional connection to remote peer within specified * timeout of #timeout. * * @param group group context * @param timeout The number of milliseconds within which the JxtaBiDiPipe must * be successfully created. An exception will be thrown if the pipe * cannot be created in the allotted time. A timeout value of {@code 0} * (zero) specifies an infinite timeout. * @param pipeAd PipeAdvertisement * @param msgListener application PipeMsgListener * @throws IOException if an io error occurs */ public JxtaBiDiPipe(PeerGroup group, PipeAdvertisement pipeAd, int timeout, PipeMsgListener msgListener) throws IOException { connect(group, null, pipeAd, timeout, msgListener); } /** * attempts to create a bidirectional connection to remote peer * * @param group group context * @param pipeAd PipeAdvertisement * @param timeout The number of milliseconds within which the JxtaBiDiPipe must * be successfully created. An exception will be thrown if the pipe * cannot be created in the allotted time. A timeout value of {@code 0} * (zero) specifies an infinite timeout. * @param msgListener application PipeMsgListener * @param reliable if true, the reliability is assumed * @throws IOException if an io error occurs */ public JxtaBiDiPipe(PeerGroup group, PipeAdvertisement pipeAd, int timeout, PipeMsgListener msgListener, boolean reliable) throws IOException { connect(group, null, pipeAd, timeout, msgListener, reliable); } /** * Connect to a JxtaServerPipe with default timeout * * @param group group context * @param pipeAd PipeAdvertisement * @throws IOException if an io error occurs */ public void connect(PeerGroup group, PipeAdvertisement pipeAd) throws IOException { connect(group, pipeAd, timeout); } /** * Connects to a remote JxtaBiDiPipe * * @param group group context * @param pipeAd PipeAdvertisement * @param timeout timeout in ms, also reset object default timeout * to that of timeout * @throws IOException if an io error occurs */ public void connect(PeerGroup group, PipeAdvertisement pipeAd, int timeout) throws IOException { connect(group, null, pipeAd, timeout, null); } /** * Connects to a remote JxtaServerPipe * * @param group group context * @param peerid peer to connect to * @param pipeAd PipeAdvertisement * @param timeout timeout in ms, also reset object default timeout to that of timeout * @param msgListener application PipeMsgListener * @throws IOException if an io error occurs */ public void connect(PeerGroup group, PeerID peerid, PipeAdvertisement pipeAd, int timeout, PipeMsgListener msgListener) throws IOException { connect(group, peerid, pipeAd, timeout, msgListener, isReliable); } /** * Connects to a remote JxtaServerPipe * * @param group group context * @param peerid peer to connect to * @param pipeAd PipeAdvertisement * @param timeout timeout in ms, also reset object default timeout to that of timeout * @param msgListener application PipeMsgListener * @param reliable Reliable connection * @throws IOException if an io error occurs */ public void connect(PeerGroup group, PeerID peerid, PipeAdvertisement pipeAd, int timeout, PipeMsgListener msgListener, boolean reliable) throws IOException { if (isBound()) { throw new IOException("Pipe already bound"); } if (timeout <= 0) { throw new IllegalArgumentException("Invalid timeout :" + timeout); } this.pipeAdv = pipeAd; this.group = group; this.msgListener = msgListener; if (msgListener == null) { queue = new ArrayBlockingQueue<PipeMsgEvent>(windowSize); } this.isReliable = reliable; pipeSvc = group.getPipeService(); this.timeout = (timeout == 0) ? Integer.MAX_VALUE : timeout; if (myPipeAdv == null) { myPipeAdv = JxtaServerPipe.newInputPipe(group, pipeAd); this.inputPipe = pipeSvc.createInputPipe(myPipeAdv, this); } this.credentialDoc = credentialDoc != null ? credentialDoc : getCredDoc(group); Message openMsg = createOpenMessage(group, myPipeAdv); // create the output pipe and send this message if (peerid == null) { pipeSvc.createOutputPipe(pipeAd, this); } else { pipeSvc.createOutputPipe(pipeAd, Collections.singleton(peerid), this); } try { synchronized (acceptLock) { // check connectOutpipe within lock to prevent a race with modification. if (connectOutpipe == null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Waiting for " + timeout + " msec"); } acceptLock.wait(timeout); } } } catch (InterruptedException ie) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Interrupted", ie); } Thread.interrupted(); IOException exp = new IOException("Interrupted"); exp.initCause(ie); throw exp; } if (connectOutpipe == null) { throw new SocketTimeoutException("Connection timeout"); } // send connect message waiting = true; if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Sending a backchannel message"); } connectOutpipe.send(openMsg); // wait for the second op try { synchronized (finalLock) { if (waiting) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Waiting for " + timeout + " msec for back channel to be established"); } finalLock.wait(timeout); // Need to check for creation if (msgr == null) { throw new SocketTimeoutException("Connection timeout"); } } } } catch (InterruptedException ie) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Interrupted", ie); } Thread.interrupted(); IOException exp = new IOException("Interrupted"); exp.initCause(ie); throw exp; } setBound(); notifyListeners(PipeStateListener.PIPE_OPENED_EVENT); } /** * creates all the reliability objects */ private void createRLib() { if (isReliable) { if (outgoing == null) { outgoing = new OutgoingMsgrAdaptor(msgr, retryTimeout); } if (ros == null) { ros = new ReliableOutputStream(outgoing, new FixedFlowControl(windowSize)); } if (ris == null) { ris = new ReliableInputStream(outgoing, retryTimeout, this); } } } /** * Toggles reliability * * @param reliable Toggles reliability to reliable * @throws IOException if pipe is bound */ public void setReliable(boolean reliable) throws IOException { if (isBound()) { throw new IOException("Can not set reliability after pipe is bound"); } this.isReliable = reliable; } /** * Obtain the cred doc from the group object. * * @param group group context * @return The credDoc value */ protected static StructuredDocument getCredDoc(PeerGroup group) { try { MembershipService membership = group.getMembershipService(); Credential credential = membership.getDefaultCredential(); if (credential != null) { return credential.getDocument(MimeMediaType.XMLUTF8); } } catch (Exception e) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "failed to get credential", e); } } return null; } /** * get the remote credential doc * * @return Credential StructuredDocument */ public StructuredDocument getCredentialDoc() { return credentialDoc; } /** * get the connection properties * * @return Properties connection property */ public Properties getConnectionProperties() { if (connectionProperties == null) { return null; } return (Properties) connectionProperties.clone(); } /** * get the connection property * * @return byte[] byte[] representation of connection property */ private byte[] getConnectionPropertiesBytes() { return propertiesToBytes(connectionProperties); } private byte[] propertiesToBytes(Properties properties) { if(properties == null) { return null; } // Write properties file. ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { properties.store(bos, null); } catch (IOException e) { } return bos.toByteArray(); } /** * Sets the connection credential doc. * If no credentials are set, the default group credential are used. * * @param doc Credential StructuredDocument */ public void setCredentialDoc(StructuredDocument doc) { this.credentialDoc = doc; } /** * Creates a connection request message * * @param group group context * @param pipeAd pipe advertisement * @return the Message object * @throws IOException if an io error occurs */ protected Message createOpenMessage(PeerGroup group, PipeAdvertisement pipeAd) throws IOException { Message msg = new Message(); PeerAdvertisement peerAdv = group.getPeerAdvertisement(); if (credentialDoc == null) { credentialDoc = getCredDoc(group); } if (credentialDoc == null && pipeAd.getType().equals(PipeService.UnicastSecureType)) { throw new IOException("No credentials established to initiate a secure connection"); } try { if (credentialDoc != null) { msg.addMessageElement(JxtaServerPipe.nameSpace, new TextDocumentMessageElement(JxtaServerPipe.credTag, (XMLDocument) credentialDoc, null)); } msg.addMessageElement(JxtaServerPipe.nameSpace, new TextDocumentMessageElement(JxtaServerPipe.reqPipeTag, (XMLDocument) pipeAd.getDocument(MimeMediaType.XMLUTF8), null)); msg.addMessageElement(JxtaServerPipe.nameSpace, new StringMessageElement(JxtaServerPipe.reliableTag, Boolean.toString(isReliable), null)); msg.addMessageElement(JxtaServerPipe.nameSpace, new StringMessageElement(JxtaServerPipe.directSupportedTag, Boolean.toString(true), null)); byte[] connectionPropertiesBytes = this.getConnectionPropertiesBytes(); if (connectionPropertiesBytes != null) { msg.addMessageElement(JxtaServerPipe.nameSpace, new ByteArrayMessageElement(JxtaServerPipe.connectionPropertiesTag, null, connectionPropertiesBytes, null)); } msg.addMessageElement(JxtaServerPipe.nameSpace, new TextDocumentMessageElement(JxtaServerPipe.remPeerTag, (XMLDocument) peerAdv.getDocument(MimeMediaType.XMLUTF8), null)); return msg; } catch (Throwable t) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "error getting element stream", t); } return null; } } /** * Sets the bound attribute of the JxtaServerPipe object */ void setBound() { bound = true; if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Pipe Bound :true"); } } /** * Returns the binding state of the JxtaServerPipe. * * @return true if the ServerSocket successfully bound to an address */ public boolean isBound() { return bound; } /** * Returns an input stream for this socket. * * @return a stream for reading from this socket. * @throws IOException if an I/O error occurs when creating the * input stream. */ public InputPipe getInputPipe() throws IOException { return inputPipe; } /** * Returns remote PeerAdvertisement * * @return remote PeerAdvertisement */ public PeerAdvertisement getRemotePeerAdvertisement() { return remotePeerAdv; } /** * Returns remote PipeAdvertisement * * @return remote PipeAdvertisement */ public PipeAdvertisement getRemotePipeAdvertisement() { return remotePipeAdv; } /** * Sets the remote PeerAdvertisement * * @param peer Remote PeerAdvertisement */ protected void setRemotePeerAdvertisement(PeerAdvertisement peer) { this.remotePeerAdv = peer; } /** * Sets the remote PipeAdvertisement * * @param pipe PipeAdvertisement */ protected void setRemotePipeAdvertisement(PipeAdvertisement pipe) { this.remotePipeAdv = pipe; } /** * Closes this pipe. * * @throws IOException if an I/O error occurs when closing this * socket. */ public void close() throws IOException { sendClose(); closePipe(false); bound = false; } protected void closePipe(boolean fastClose) throws IOException { // close both pipes synchronized (closeLock) { if (closed) { return; } closed = true; bound = false; } if (!fastClose && isReliable && !direct) { /* * This implements linger! */ long quitAt = System.currentTimeMillis() + timeout; while (true) { //FIXME hamada this does not loop if (ros == null || ros.getMaxAck() == ros.getSeqNumber()) { // Nothing to worry about. break; } // By default wait forever. long left = 0; // If timeout is not zero. Then compute the waiting time // left. if (timeout != 0) { left = quitAt - System.currentTimeMillis(); if (left < 0) { // Too late sendClose(); throw new SocketTimeoutException("Close timeout"); } } try { if (!ros.isQueueEmpty()) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Waiting for Output stream queue event"); } ros.waitQueueEvent(left); } break; } catch (InterruptedException ie) { // give up, then. throw new IOException("Close interrupted"); } } // We are initiating the close. We do not want to receive // anything more. So we can close the ris right away. ris.close(); } if (isReliable && ros != null) { ros.close(); } // close the pipe inputPipe.close(); msgr.close(); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Pipe close complete"); } notifyListeners(PIPE_CLOSED_EVENT); } private void notifyListeners(int event) { try { if (eventListener != null) { eventListener.pipeEvent(event); } else if (stateListener != null) { stateListener.stateEvent(this, event); } } catch (Throwable th) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "error during pipe event callback", th); } } } /** * Sets the inputPipe attribute of the JxtaBiDiPipe object * * @param inputPipe The new inputPipe value */ protected void setInputPipe(InputPipe inputPipe) { this.inputPipe = inputPipe; } /** * {@inheritDoc} */ public void pipeMsgEvent(PipeMsgEvent event) { Message message = event.getMessage(); if (message == null) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Empty event"); } return; } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Pipe message arrived"); } MessageElement element; if (!bound) { // look for a remote pipe answer element = message.getMessageElement(JxtaServerPipe.nameSpace, JxtaServerPipe.remPipeTag); if (element != null) { // connect response try { XMLDocument CredDoc = null; XMLDocument remotePipeDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(element); remotePipeAdv = (PipeAdvertisement) AdvertisementFactory.newAdvertisement(remotePipeDoc); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Recevied a pipe Advertisement :" + remotePipeAdv.getName()); } element = message.getMessageElement(JxtaServerPipe.nameSpace, JxtaServerPipe.remPeerTag); if (element != null) { XMLDocument remotePeerDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(element); remotePeerAdv = (PeerAdvertisement) AdvertisementFactory.newAdvertisement(remotePeerDoc); if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Recevied an Peer Advertisement :" + remotePeerAdv.getName()); } } else { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning(" BAD connect response"); } return; } element = message.getMessageElement(JxtaServerPipe.nameSpace, JxtaServerPipe.credTag); if (element != null) { CredDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(element); } if (pipeAdv.getType().equals(PipeService.UnicastSecureType) && (CredDoc == null || !checkCred(CredDoc))) { // we're done here if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.severe("Missing remote credential doc"); } return; } element = message.getMessageElement(JxtaServerPipe.nameSpace, JxtaServerPipe.reliableTag); if (element != null) { isReliable = Boolean.valueOf(element.toString()); } boolean directSupported = false; element = message.getMessageElement(JxtaServerPipe.nameSpace, JxtaServerPipe.directSupportedTag); if (element != null) { directSupported = Boolean.valueOf(element.toString()); } if (directSupported) { msgr = getDirectMessenger(group, remotePipeAdv, remotePeerAdv); if (msgr != null) { this.direct = true; } else { msgr = lightweightOutputPipe(group, remotePipeAdv, remotePeerAdv); } } else { msgr = lightweightOutputPipe(group, remotePipeAdv, remotePeerAdv); } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Reliability set to :" + isReliable); } if (isReliable && !direct) { createRLib(); } synchronized (finalLock) { waiting = false; finalLock.notifyAll(); } } catch (IOException e) { if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) { LOG.log(Level.SEVERE, "failed to process response message", e); } } return; } } if (isReliable && !direct) { // let reliabilty deal with the message receiveMessage(message); return; } if (!hasClose(message)) { push(event); } } private boolean hasClose(Message message) { // look for close request MessageElement element = message.getMessageElement(JxtaServerPipe.nameSpace, JxtaServerPipe.closeTag); if (element != null) { try { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Received a pipe close request, closing pipes"); } if (ros != null) { ros.hardClose(); } closePipe(false); } catch (IOException ie) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "failed during close", ie); } } return true; } return false; } private void receiveMessage(Message message) { Iterator<MessageElement> i = message.getMessageElements(Defs.NAMESPACE, Defs.MIME_TYPE_ACK); if (i.hasNext()) { if (ros != null) { ros.recv(message); } return; } i = message.getMessageElements(Defs.NAMESPACE, Defs.MIME_TYPE_BLOCK); if (i.hasNext()) { // It can happen that we receive messages for the input stream // while we have not finished creating it. try { synchronized (finalLock) { while (waiting) { finalLock.wait(timeout); } } } catch (InterruptedException ie) {// ignored } if (ris != null) { ris.recv(message); } } } /** * Gets the Maximum Retry Timeout of the reliability layer * * @return The maximum retry Timeout value */ public synchronized int getMaxRetryTimeout() { return maxRetryTimeout; } /** * Gets the Maximum Retry Timeout of the reliability layer * * @param maxRetryTimeout The new maximum retry timeout value * @throws IllegalArgumentException if maxRetryTimeout exceeds jxta platform maximum retry timeout */ public synchronized void setMaxRetryTimeout(int maxRetryTimeout) { if (maxRetryTimeout <= 0 || maxRetryTimeout > MAXRETRYTIMEOUT) { throw new IllegalArgumentException("Invalid Maximum retry timeout :" + maxRetryTimeout + " Exceed Global maximum retry timeout :" + MAXRETRYTIMEOUT); } this.maxRetryTimeout = maxRetryTimeout; } /** * Gets the Retry Timeout of the reliability layer * * @return The retry Timeout value */ public synchronized int getRetryTimeout() { return retryTimeout; } /** * Sets the Retry Timeout of the underlying reliability layer * . * In reliable mode it is possible for this call to block * trying to obtain a lock on reliable input stream * * @param retryTimeout The new retry timeout value * @throws IOException if an I/O error occurs */ public synchronized void setRetryTimeout(int retryTimeout) throws IOException { if (timeout <= 0) { throw new IllegalArgumentException("Invalid Socket timeout :" + retryTimeout); } this.retryTimeout = retryTimeout; if (outgoing != null) { outgoing.setTimeout(retryTimeout); } } /** * When in reliable mode, gets the Reliable library window size * * @return The windowSize value */ public synchronized int getWindowSize() { return windowSize; } /** * When in reliable mode, sets the Reliable library window size * * @param windowSize The new window size value * @throws IOException if an I/O error occurs */ public synchronized void setWindowSize(int windowSize) throws IOException { if (isBound()) { throw new IOException("Socket bound. Can not change the window size"); } this.windowSize = windowSize; } /** * This method is invoked by the Reliability library for each incoming data message * * @param message Incoming message */ public void processIncomingMessage(Message message) { if (!hasClose(message)) { PipeMsgEvent event = new PipeMsgEvent(this, message, (PipeID) this.pipeAdv.getID()); push(event); } } private void push(PipeMsgEvent event) { boolean queued = false; BlockingQueue<PipeMsgEvent> msg_queue = queue; if (null != msg_queue) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("push message onto queue"); } queued = msg_queue.offer(event); } PipeMsgListener msg_event_listener = msgListener; if (!queued && (null != msg_event_listener)) { msg_event_listener.pipeMsgEvent(event); } } /** * Send a message * <p/> * <code>Messenger</code> * * @param msg Message to send to the remote side * @return true if message was successfully enqueued * @throws IOException if the underlying messenger breaks, either due to * a physical address change, reliability issue. * @see net.jxta.endpoint.Message */ public boolean sendMessage(Message msg) throws IOException { if (isReliable && !direct) { int seqn = ros.send(msg); return (seqn > 0); } else { try { if (msgr instanceof TcpMessenger) { ((TcpMessenger) msgr).sendMessageDirect(msg, null, null, true); return true; } else { return msgr.sendMessage(msg, null, null); } } catch (SocketTimeoutException io) { if (msgr instanceof TcpMessenger) { ((TcpMessenger) msgr).sendMessageDirect(msg, null, null, true); return true; } else { return msgr.sendMessage(msg, null, null); } } catch (IOException io) { closePipe(true); IOException exp = new IOException("IO error occured during sendMessage()"); exp.initCause(io); throw exp; } } } /** * {@inheritDoc} */ public void outputPipeEvent(OutputPipeEvent event) { OutputPipe op = event.getOutputPipe(); if (op.getAdvertisement() == null) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("The output pipe has no internal pipe advertisement. Continueing anyway."); } } if (op.getAdvertisement() == null || pipeAdv.equals(op.getAdvertisement())) { synchronized (acceptLock) { // modify op within lock to prevent a race with the if. if (connectOutpipe == null) { connectOutpipe = op; // set to null to avoid closure op = null; } acceptLock.notifyAll(); } // Ooops one too many, we were too fast re-trying. if (op != null) { op.close(); } } else { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("Unexpected OutputPipe :" + op); } } } /** * A lightweight direct messenger output pipe constructor, note the return type * Since all the info needed is available, there's no need for to * use the pipe service to resolve the pipe we have all we need * to construct a messenger. * * @param group group context * @param pipeAdv Remote Pipe Advertisement * @param peer Remote Peer advertisement * @return Messenger * * @since 2.6 Direct messengers cause connectivity issues. One should not rely on * corresponding code anymore. * */ @Deprecated protected static Messenger getDirectMessenger(PeerGroup group, PipeAdvertisement pipeAdv, PeerAdvertisement peer) { // Get an endpoint messenger to that address if (pipeAdv.getType().equals(PipeService.PropagateType)) { throw new IllegalArgumentException("Invalid pipe type " + pipeAdv.getType()); } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Creating a Direct Messenger"); } if (pipeAdv.getType().equals(PipeService.UnicastType)) { EndpointService endpoint = group.getEndpointService(); EndpointAddress pipeEndpoint = new EndpointAddress("jxta", (peer.getPeerID().getUniqueValue()).toString(), "PipeService", pipeAdv.getPipeID().toString()); return endpoint.getDirectMessenger(pipeEndpoint, peer, true); } return null; } /** * A lightweight output pipe constructor, note the return type * Since all the info needed is available, there's no need for to * use the pipe service to resolve the pipe we have all we need * to construct a messenger. * * @param group group context * @param pipeAdv Remote Pipe Advertisement * @param peer Remote Peer advertisement * @return Messenger */ protected static Messenger lightweightOutputPipe(PeerGroup group, PipeAdvertisement pipeAdv, PeerAdvertisement peer) { EndpointService endpoint = group.getEndpointService(); ID opId = pipeAdv.getPipeID(); String destPeer = (peer.getPeerID().getUniqueValue()).toString(); // Get an endpoint messenger to that address EndpointAddress addr; if (pipeAdv.getType().equals(PipeService.UnicastType)) { addr = new EndpointAddress("jxta", destPeer, "PipeService", opId.toString()); } else if (pipeAdv.getType().equals(PipeService.UnicastSecureType)) { addr = new EndpointAddress("jxtatls", destPeer, "PipeService", opId.toString()); } else { // not a supported type return null; } if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("Creating a lightweightOutputPipe()"); } return endpoint.getMessenger(addr); } /** * Not implemented yet * * @param cred the credential document * @return always returns true */ protected boolean checkCred(StructuredDocument cred) { // FIXME need to check credentials return true; } /** * Send a close message to the remote side */ private void sendClose() { if (!direct && isReliable && ros.isClosed()) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.fine("ReliableOutputStream is already closed. Skipping close message"); } return; } Message msg = new Message(); msg.addMessageElement(JxtaServerPipe.nameSpace, new StringMessageElement(JxtaServerPipe.closeTag, "close", null)); try { sendMessage(msg); // ros will not take any new message, now. if (!direct && ros != null) { ros.close(); } } catch (IOException ie) { if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) { LOG.log(Level.SEVERE, "failed during close", ie); } } } /** * Returns the message listener for this pipe * * @return PipeMsgListener */ public PipeMsgListener getMessageListener() { return msgListener; } /** * Sets message listener for a pipe spawned by the JxtaServerPipe. * There is a window where a message could arrive prior to listener being * registered therefore a message queue is created to queue messages, once * a listener is registered these messages will be dequeued by calling the * listener until the queue is empty. * <p/> * Sending messages vis {@link #sendMessage(Message)} from within a * {@code PipeMsgListener} may result in a deadlock due to contention * between the sending and receiving portions of BiDi pipes. * * @param msgListener New value of property listener. */ public void setMessageListener(PipeMsgListener msgListener) { BlockingQueue<PipeMsgEvent> drainQueue = null; synchronized (this) { this.msgListener = msgListener; if (null != msgListener) { drainQueue = queue; queue = null; } else { queue = new ArrayBlockingQueue<PipeMsgEvent>(windowSize); } } if (null != drainQueue) { while (!drainQueue.isEmpty()) { PipeMsgEvent event = drainQueue.poll(); if (null != event) { push(event); } } } } /** * Sets a Pipe event listener, set listener to null to unset the listener. * * @param eventListener New value of property listener. */ public void setPipeEventListener(PipeEventListener eventListener) { this.eventListener = eventListener; } /** * Returns the Pipe event listener for this pipe * * @return PipeMsgListener */ public PipeEventListener getPipeEventListener() { return eventListener; } /** * Sets a Pipe state listener, set listener to null to unset the listener * * @param stateListener New value of property listener. */ public void setPipeStateListener(PipeStateListener stateListener) { this.stateListener = stateListener; } /** * Returns the Pipe state listener for this pipe * * @return PipeMsgListener */ public PipeStateListener getPipeStateListener() { return stateListener; } /** * Gets a message from the queue. If no Object is immediately available, * then wait the specified amount of time for a message to be inserted. * * @param timeout Amount of time to wait in milliseconds for an object to * be available. Per Java convention, a timeout of zero (0) means wait an * infinite amount of time. Negative values mean do not wait at all. * @return The next message in the queue. if a listener is registered calls * to this method will return null * @throws InterruptedException if the operation is interrupted before * the timeout interval is completed. */ public Message getMessage(long timeout) throws InterruptedException { BlockingQueue<PipeMsgEvent> msg_queue = queue; if (msg_queue == null) { return null; } else { if (0 == timeout) { timeout = Long.MAX_VALUE; } PipeMsgEvent ev = msg_queue.poll(timeout, TimeUnit.MILLISECONDS); if (ev != null) { return ev.getMessage(); } else { return null; } } } /** * Returns the Assigned PipeAdvertisement * * @return the Assigned PipeAdvertisement */ public PipeAdvertisement getPipeAdvertisement() { return pipeAdv; } /** * {@inheritDoc} * <p/> * Closes the JxtaBiDiPipe. */ @Override protected void finalize() throws Throwable { try { if (!closed) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.warning("JxtaBiDiPipe is being finalized without being previously closed. This is likely a users bug."); } close(); } } finally { super.finalize(); } } }