/**
* The MIT License
*
* Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.hudsonci.utils.tasks;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.RemoteInputStream;
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.SocketInputStream;
import hudson.remoting.SocketOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
/**
* Opens a server socket on a node and facilitates accepting connections.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.1.0
*/
public class OpenServerSocket
implements Callable<OpenServerSocket.Acceptor,IOException>
{
private static final Logger log = LoggerFactory.getLogger(OpenServerSocket.class);
public static final int DEFAULT_SO_TIMEOUT = 30 * 1000;
/**
* Used to accept a new connection on the remote server socket.
*/
public interface Acceptor
extends Closeable
{
int getPort();
Connection accept(boolean close) throws IOException;
Connection accept() throws IOException;
}
/**
* Represents the accepted connection.
*/
public interface Connection
extends Closeable
{
InputStream getInput();
OutputStream getOutput();
}
public Acceptor call() throws IOException {
return new AcceptorImpl();
}
protected void customize(final ServerSocket serverSocket) throws SocketException {
assert serverSocket != null;
serverSocket.setSoTimeout(DEFAULT_SO_TIMEOUT);
}
// TODO: Document behavior on both sides of channel so its easier to comprehend whats going on here
private class AcceptorImpl
implements Acceptor, Serializable
{
private static final long serialVersionUID = 1L;
private transient final ServerSocket serverSocket;
public AcceptorImpl() throws IOException {
serverSocket = new ServerSocket();
serverSocket.bind(null);
customize(serverSocket);
log.debug("Created acceptor");
}
public Connection accept(final boolean close) throws IOException {
log.debug("Accepting");
Socket socket = serverSocket.accept();
log.debug("Accepted: {}", socket);
if (close) {
close();
}
return new ConnectionImpl(socket);
}
public Connection accept() throws IOException {
return accept(false);
}
public void close() throws IOException {
log.debug("Closing");
serverSocket.close();
}
public int getPort() {
return serverSocket.getLocalPort();
}
/**
* Executed on remote, returns a proxy.
*/
private Object writeReplace() {
return Channel.current().export(Acceptor.class, this);
}
}
private class ConnectionImpl
implements Connection, Serializable
{
private static final long serialVersionUID = 1L;
private InputStream input;
private OutputStream output;
public ConnectionImpl(final InputStream input, final OutputStream output) {
assert input != null;
this.input = input;
assert output != null;
this.output = output;
log.debug("Created connection");
}
private ConnectionImpl(final Socket socket) throws IOException {
// assert socket != null
this(new SocketInputStream(socket), new SocketOutputStream(socket));
// TODO: How to close the socket?
}
public InputStream getInput() {
return input;
}
public OutputStream getOutput() {
return output;
}
public void close() throws IOException {
// FIXME: nop for now, trying to close the streams causes problems
}
/**
* Executed on remote, returns connection wrapper with remote streams.
*/
private Object writeReplace() {
return new ConnectionImpl(new RemoteInputStream(input), new RemoteOutputStream(output));
}
/**
* Re-establishes buffering.
*/
private Object readResolve() {
this.input = new BufferedInputStream(input);
this.output = new BufferedOutputStream(output);
return this;
}
}
}