package com.trendmicro.mist.session;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Collection;
import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.trendmicro.mist.Client;
import com.trendmicro.mist.Daemon;
import com.trendmicro.mist.MistException;
import com.trendmicro.mist.proto.GateTalk;
import com.trendmicro.mist.util.Exchange;
public abstract class Session implements Runnable {
public static final String MIST_MESSAGE_TTL = "MIST_TTL";
protected final static Logger logger = LoggerFactory.getLogger(Session.class);
private ServerSocket localServer;
protected Socket socket;
protected BufferedInputStream socketInput;
protected BufferedOutputStream socketOutput;
protected volatile boolean detachNow = false;
protected HashMap<Exchange, Client> allClients = new HashMap<Exchange, Client>();
protected boolean determinedConnection = false;
protected int sessionId;
protected GateTalk.Session sessionConfig;
protected boolean isReady = false;
protected Thread sessionThread = null;
private Object attachLock = new Object();
/**
* Helper function to accept an incoming connection from the client. The
* localServer is used one time only and will not accept more then one
* connection.
*
* @return True - If the connection and the socket IO streams has been set
* up correctly<br>
* False - Fail to accept the connection or session is detached
*/
protected boolean acceptConnection() {
socket = null;
socketInput = null;
socketOutput = null;
try {
for(int i = 0; i < 60; i++) {
try {
socket = localServer.accept();
localServer.close();
localServer = null;
break;
}
catch(SocketTimeoutException e) {
if(detachNow) {
cleanupSockets();
return false;
}
}
}
if(socket == null) {
throw new IOException("cannot accept client connection (timed out)");
}
socket.setTcpNoDelay(true);
socketInput = new BufferedInputStream(socket.getInputStream());
socketOutput = new BufferedOutputStream(socket.getOutputStream());
return true;
}
catch(IOException e) {
cleanupSockets();
return false;
}
}
/**
* Helper function to clean up the sockets
*/
private void cleanupSockets() {
if(localServer != null) {
try {
localServer.close();
}
catch(Exception e) {
}
}
if(socketOutput != null) {
try {
socketOutput.close();
}
catch(Exception e) {
}
}
if(socketInput != null) {
try {
socketInput.close();
}
catch(Exception e) {
}
}
if(socket != null) {
try {
socket.close();
}
catch(Exception e) {
}
}
}
private void checkRole(GateTalk.Request.Role role) throws MistException {
if(this instanceof ProducerSession) {
if(role == GateTalk.Request.Role.SOURCE)
throw new MistException(MistException.INCOMPATIBLE_TYPE_SINK);
}
else {
if(role == GateTalk.Request.Role.SINK)
throw new MistException(MistException.INCOMPATIBLE_TYPE_SOURCE);
}
}
public Session(int sessId, GateTalk.Session sessConfig) throws MistException {
this.sessionId = sessId;
this.sessionConfig = sessConfig;
}
public int getCommPort() {
if(localServer == null)
return -1;
else
return localServer.getLocalPort();
}
@Override
public abstract void run();
public abstract void addClientIfAttached(Client c);
/**
* All clients under this session will be opened
*
* @param isResume
*/
public void open(boolean isResume) {
for(Client c : allClients.values()) {
try {
c.openClient(determinedConnection, isResume, false);
}
catch(Exception e) {
logger.error(e.getMessage());
}
}
}
/**
* All clients under this session will be opened
*
* @param isPause
*/
public void close(final boolean isPause) {
// A consumer thread is not allowed to close jms session, so fork a
// thread here to close them
Thread closingThread = new Thread() {
@Override
public void run() {
setName(sessionId + "-closer");
for(Client c : allClients.values()) {
c.closeClient(isPause, false);
}
detachNow = false;
synchronized(Daemon.closingThreadSet) {
Daemon.closingThreadSet.remove(this);
}
}
};
closingThread.start();
synchronized(Daemon.closingThreadSet) {
Daemon.closingThreadSet.add(closingThread);
}
}
/**
* Create a server socket and starts a serving thread
*
* @param role
* Whether it is a SOURCE or a SINK
* @throws MistException
*/
public void attach(GateTalk.Request.Role role) throws MistException {
checkRole(role);
while(detachNow) {
try {
Thread.sleep(100);
}
catch(InterruptedException e) {
return;
}
}
synchronized(attachLock) {
if(isAttached())
throw new MistException(MistException.ALREADY_ATTACHED);
try {
localServer = new ServerSocket();
localServer.setReuseAddress(true);
localServer.setSoTimeout(1000);
localServer.bind(null);
}
catch(IOException e) {
logger.error(e.getMessage());
try {
localServer.close();
}
catch(Exception e2) {
}
throw new MistException(e.getMessage());
}
detachNow = false;
isReady = false;
sessionThread = new Thread(this);
sessionThread.setName("Session-" + sessionId);
sessionThread.start();
}
}
protected abstract void detach();
public void detach(GateTalk.Request.Role role) throws MistException {
checkRole(role);
synchronized(attachLock) {
detachNow = true;
detach();
close(false);
cleanupSockets();
try {
sessionThread.join();
}
catch(Exception e) {
}
}
}
/**
* Add a producer / consumer client to the session
*
* @param client
* The client to be added
* @throws MistException
*/
public Client addClient(GateTalk.Client clientConfig) throws MistException {
// A producer client cannot be added to a source session and vice versa
checkRole(clientConfig.getType() == GateTalk.Client.Type.CONSUMER ? GateTalk.Request.Role.SOURCE: GateTalk.Request.Role.SINK);
synchronized(allClients) {
// Check if the client is already mounted, if not, add to the map
if(allClients.containsKey(new Exchange(clientConfig.getChannel().getName())))
throw new MistException(MistException.ALREADY_MOUNTED);
Client c = new Client(clientConfig, sessionConfig);
if(isAttached()){
c.openClient(determinedConnection, false, false);
addClientIfAttached(c);
}
allClients.put(c.getExchange(), c);
return c;
}
}
public void removeClient(GateTalk.Client clientConfig) throws MistException {
checkRole(clientConfig.getType() == GateTalk.Client.Type.CONSUMER ? GateTalk.Request.Role.SOURCE: GateTalk.Request.Role.SINK);
synchronized(allClients) {
if(allClients.isEmpty())
throw new MistException(MistException.EMPTY_SESSION);
Exchange exchange = new Exchange((clientConfig.getChannel().getType() == GateTalk.Channel.Type.QUEUE ? "queue": "topic") + ":" + clientConfig.getChannel().getName());
Client c = findClient(exchange);
if(c == null)
throw new MistException(MistException.exchangeNotFound(exchange.toString()));
c.closeClient(false, false);
allClients.remove(exchange);
}
}
public void migrateClient(Exchange exchange) {
logger.info("migrating " + exchange + "; session " + sessionId);
try {
GateTalk.Client clientConfig = findClient(exchange).getConfig();
removeClient(findClient(exchange).getConfig());
addClient(clientConfig);
}
catch(Exception e) {
logger.error(e.getMessage(), e);
}
}
public Client findClient(Exchange exchange) {
return allClients.get(exchange);
}
public boolean isReady() {
return isReady;
}
public abstract boolean isAttached();
public Collection<Client> getClientList() {
return allClients.values();
}
}