/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Initial developer(s): Robert Hodges
* Contributor(s):
*/
package com.continuent.tungsten.common.sockets;
import com.continuent.tungsten.common.security.AuthenticationInfo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.channels.ServerSocketChannel;
import java.security.GeneralSecurityException;
import javax.net.SocketFactory;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import org.apache.log4j.Logger;
import com.continuent.tungsten.common.config.cluster.ConfigurationException;
import com.continuent.tungsten.common.security.AuthenticationInfo;
import com.continuent.tungsten.common.security.SecurityHelper;
/**
* Provides a wrapper for managing server-side Socket connections. This class
* encapsulates SSL vs. non-SSL operation, and closing the connection. The code
* assumes properties required for SSL operation have been previously set before
* SSL sockets are allocated.
*
* @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a>
*/
public class ServerSocketService
{
private static Logger logger = Logger.getLogger(ServerSocketService.class);
// Properties
InetSocketAddress address;
private boolean useSSL;
private String keystoreAlias = null;
private AuthenticationInfo securityInfo = null;
private int acceptTimeout = 1000;
// Currently open server socket.
private ServerSocket serverSocket = null;
// Flag to signal service has been shut down.
private volatile boolean done = false;
/** Creates a new wrapper for client connections. */
public ServerSocketService()
{
}
public InetSocketAddress getAddress()
{
return address;
}
/** Sets the address to which we should connect. */
public void setAddress(InetSocketAddress address)
{
this.address = address;
}
public boolean isUseSSL()
{
return useSSL;
}
/** If set to true, use an SSL socket, otherwise use plain TCP/IP. */
public void setUseSSL(boolean useSSL)
{
this.setUseSSL(useSSL, null, null);
}
public void setUseSSL(boolean useSSL, String keystoreAlias,
AuthenticationInfo securityInfo)
{
this.useSSL = useSSL;
this.keystoreAlias = keystoreAlias;
this.securityInfo = securityInfo;
}
// Accessors to server socket data.
public int getLocalPort()
{
if (serverSocket != null)
return serverSocket.getLocalPort();
else
return -1;
}
/**
* Time in milliseconds before timeout to check for termination when
* accepting connections. When using SSL this must be set to a low value as
* SSL sockets are not interruptible during this operation.
*/
public void setAcceptTimeout(int acceptTimeout)
{
this.acceptTimeout = acceptTimeout;
}
/**
* Connect to the server socket.
*
* @throws GeneralSecurityException
* @throws ConfigurationException
*/
public ServerSocket bind() throws IOException, GeneralSecurityException,
ConfigurationException
{
// Create the serverSocket.
if (useSSL)
{
// Create an SSL socket.
// Use custom Socket Factory Generator to allow multiple aliases in
// single keystore
// TUC-23399
SSLSocketFactoryGenerator sslFactoryGenerator = new SSLSocketFactoryGenerator(
this.keystoreAlias, this.securityInfo);
// Will use default ssl socket factory if no alias was specified
SSLServerSocketFactory sslserverSocketFactory = sslFactoryGenerator
.getSSLServerSocketFactory();
serverSocket = (SSLServerSocket) sslserverSocketFactory
.createServerSocket();
}
else
{
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverSocket = serverChannel.socket();
}
// Bind to the address. According to Javadoc setReuseAddress() call must
// occur before bind() or results may be undefined.
serverSocket.setReuseAddress(true);
serverSocket.bind(address);
serverSocket.setSoTimeout(acceptTimeout);
return serverSocket;
}
/**
* Accepts a connection the server socket.
*
* @return A socket wrapper
* @throws SocketTerminationException Thrown if the server socket has been
* terminated by a call to {@link #close()}
* @throws IOException Thrown if a generic exception occurs during I/O
*/
public SocketWrapper accept() throws IOException
{
for (;;)
{
try
{
Socket sock = serverSocket.accept();
return new SocketWrapper(sock);
}
catch (SocketTimeoutException e)
{
// This is expected, since we time out on accept to
// prevent hangs.
}
catch (IOException e)
{
if (done)
{
throw new SocketTerminationException(
"Server socket has been terminated", e);
}
else
{
throw e;
}
}
}
}
/** Returns the server socket. */
public ServerSocket getServerSocket()
{
return this.serverSocket;
}
/**
* Close server socket. Client must call this from a separate thread to
* break hangs on bind() and accept() calls. This is synchronized to prevent
* accidental double calls.
*/
public synchronized void close()
{
if (!done)
{
if (serverSocket != null && !serverSocket.isClosed())
{
try
{
serverSocket.close();
done = true;
}
catch (IOException e)
{
logger.warn(e.getMessage());
}
}
else
{
logger.warn("Unable to close server socket (null or already closed)");
}
}
}
}