/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.narayana.blacktie.jatmibroker.core.server;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jboss.narayana.blacktie.jatmibroker.core.ResponseMonitor;
import org.jboss.narayana.blacktie.jatmibroker.core.transport.EventListener;
import org.jboss.narayana.blacktie.jatmibroker.core.transport.Message;
import org.jboss.narayana.blacktie.jatmibroker.xatmi.Connection;
/**
* Socket Server for client call back
* @author zhfeng
*
*/
public class SocketServer implements Runnable {
private static final Logger log = LogManager.getLogger(SocketServer.class);
private int port;
private String addr;
private ServerSocket serverSocket;
private boolean shutdown;
private List<Thread> threads;
private List<Client> clients;
private List<ClientContext> contexts;
private static SocketServer instance;
private static int reference;
private Thread serverThread;
public static synchronized SocketServer getInstance(Properties properties) throws IOException {
if (instance == null) {
int port = Integer.parseInt(properties.getProperty("blacktie.java.socketserver.port"));
String addr = properties.getProperty("blacktie.java.socketserver.host", "localhost");
instance = new SocketServer(port, addr);
reference = 0;
}
reference ++;
return instance;
}
public static synchronized void discardInstance() {
reference --;
if(reference == 0) {
instance.shutdown();
instance = null;
}
}
private SocketServer(int port, String addr) throws IOException {
this.shutdown = false;
this.addr = addr;
serverSocket = new ServerSocket(port);
this.port = serverSocket.getLocalPort();
clients = new ArrayList<Client>();
threads = new ArrayList<Thread>();
contexts = new ArrayList<ClientContext>();
serverThread = new Thread(this);
serverThread.setDaemon(true);
serverThread.start();
}
public int getPort() {
return port;
}
public String getAddr() {
return addr;
}
public Socket getClientSocket(int sid) {
ClientContext context = getContext(sid);
if(context != null) return context.getSocket();
else return null;
}
public synchronized ClientContext register(int sid, ResponseMonitor responseMonitor, EventListener eventListener) {
for(int i = 0; i < contexts.size(); i++) {
if(contexts.get(i).getSid() == sid) {
return contexts.get(i);
}
}
ClientContext context = new ClientContext();
context.setSid(sid);
context.setResponseMonitor(responseMonitor);
context.setEventListener(eventListener);
contexts.add(context);
log.debug("register sid " + sid);
return context;
}
public synchronized void unregister(int sid) {
for(int i = 0; i < contexts.size(); i++) {
if(contexts.get(i).getSid() == sid) {
contexts.remove(i);
log.debug("unregister sid " + sid);
}
}
}
public Message receiveMessage(int sid, long timeout) {
ClientContext context = this.getContext(sid);
Message msg = null;
if(context != null) {
synchronized(context) {
msg = context.getMessage(timeout);
}
} else {
log.warn("Could not receive message for sid " + sid);
}
return msg;
}
public ClientContext getContext(int sid) {
for(int i = 0; i < contexts.size(); i++) {
if(contexts.get(i).getSid() == sid) {
return contexts.get(i);
}
}
return null;
}
private void shutdown() {
log.debug("shutdowning server");
try {
shutdown = true;
for(int i = 0; i < threads.size(); i++) {
try {
clients.get(i).close();
threads.get(i).join();
} catch (InterruptedException e) {
log.error("join client " + i + " failed with " + e);
}
}
if(serverSocket != null) {
serverSocket.close();
}
serverThread.join();
} catch (IOException e) {
log.error("close server socket failed with " + e);
} catch (InterruptedException e) {
log.error("Could not join serverThread with " + e);
}
log.debug("shutdown server");
}
protected void finalize() {
this.shutdown();
}
/* running to accept client connection and handle the message.
*
*/
public void run() {
while(!shutdown) {
try {
Socket clientSocket = serverSocket.accept();
log.debug("connection from " + clientSocket);
Client client = new Client(this, clientSocket);
clients.add(client);
Thread thread = new Thread(client);
threads.add(thread);
thread.start();
} catch(SocketException e) {
} catch(IOException e) {
log.error("run server failed with " + e);
}
}
}
}
class Client implements Runnable {
private static final Logger log = LogManager.getLogger(Client.class);
private Socket socket;
private int sid;
private boolean isClose;
private SocketServer server;
public Client(SocketServer server, Socket socket) {
this.sid = -1;
this.socket = socket;
this.isClose = false;
this.server = server;
}
public boolean isClose() {
return isClose;
}
public int getSid() {
return sid;
}
public void close() {
if(!isClose && socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
public void run() {
try {
DataInputStream ins = new DataInputStream(socket.getInputStream());
int size;
while((size = ins.readInt()) != -1) {
log.debug("size is " + size);
Message message = new Message();
byte[] buf = new byte[size];
int readn = 0;
int remain = size;
while(remain > 0) {
int n;
n = ins.read(buf, readn, remain);
if(n != -1) {
remain -= n;
readn += n;
} else {
log.error("expect " + size + " but read " + readn);
break;
}
}
if(remain == 0) {
log.debug("receive from " + socket + " and size is " + size + " buffer is " + buf);
String[] s = new String(buf).split("\n");
log.debug("sid is " + s[0]);
sid = Integer.parseInt(s[0]);
log.debug("cd is " + s[1]);
message.cd = Integer.parseInt(s[1]);
log.debug("rcode is " + s[2]);
message.rcode = Integer.parseInt(s[2]);
log.debug("len is " + s[3]);
message.len = Integer.parseInt(s[3]);
log.debug("flags is " + s[4]);
message.flags = Integer.parseInt(s[4]);
log.debug("rval is " + s[5]);
message.rval = Short.parseShort(s[5]);
log.debug("replyto is " + s[6]);
message.replyTo = s[6].equals("(null)") ? null : s[6];
log.debug("type is " + s[7]);
message.type = s[7].equals("(null)") ? null : s[7];
log.debug("subtype is " + s[8]);
message.subtype = s[8].equals("(null)") ? null : s[8];
message.data = new byte[message.len];
System.arraycopy(buf, size - message.len, message.data, 0, message.len);
log.debug("data is " + new String(message.data));
ClientContext context = server.getContext(sid);
if(context != null) {
synchronized(context) {
EventListener eventListener = context.getEventListener();
if (eventListener != null) {
log.debug("Event listener will be called back");
if (message.rval == EventListener.DISCON_CODE) {
eventListener.setLastEvent(Connection.TPEV_DISCONIMM, message.rcode);
} else if (message.rcode == Connection.TPESVCERR) {
eventListener.setLastEvent(Connection.TPEV_SVCERR, message.rcode);
} else if (message.rval == Connection.TPFAIL) {
eventListener.setLastEvent(Connection.TPEV_SVCFAIL, message.rcode);
}
}
context.getData().add(message);
log.debug("add message to context " + context.getSid());
context.setSocket(socket);
ResponseMonitor responseMonitor = context.getResponseMonitor();
if(responseMonitor != null) {
responseMonitor.responseReceived(sid, false);
}
log.debug("notifying");
context.notify();
log.debug("notified");
}
}
}
}
socket.shutdownInput();
isClose = true;
} catch (EOFException e) {
log.debug("client " + socket + " close");
isClose = true;
} catch (SocketException e) {
isClose = true;
} catch (IOException e) {
log.error("client " + socket + " run failed with " + e);
}
}
}
class ClientContext {
private static final Logger log = LogManager.getLogger(ClientContext.class);
private int sid;
private List<Message> data;
private Socket socket;
private ResponseMonitor responseMonitor;
private EventListener eventListener;
public ClientContext() {
this.data = new ArrayList<Message>();
}
public void setSid(int sid) {
this.sid = sid;
}
public int getSid() {
return sid;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public Socket getSocket() {
return socket;
}
public List<Message> getData() {
return data;
}
public void setResponseMonitor(ResponseMonitor responseMonitor) {
this.responseMonitor = responseMonitor;
}
public ResponseMonitor getResponseMonitor() {
return responseMonitor;
}
public void setEventListener(EventListener eventListener) {
this.eventListener = eventListener;
}
public EventListener getEventListener() {
return eventListener;
}
public Message getMessage(long timeout) {
log.debug("receive message for context " + sid);
if(data.isEmpty()) {
synchronized(this) {
try {
log.debug("waiting for " + timeout);
wait(timeout);
log.debug("waited");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if(data.isEmpty()) {
return null;
} else {
return data.remove(0);
}
}
}