package org.kevoree.library.socketChannel;
/**
* Created by IntelliJ IDEA.
* User: jed
* Date: 07/10/11
* Time: 09:30
* To change this template use File | Settings | File Templates.
*/
import org.kevoree.annotation.ChannelTypeFragment;
import org.kevoree.annotation.*;
import org.kevoree.framework.*;
import org.kevoree.framework.message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Library(name = "JavaSE", names = {"Android"})
@ChannelTypeFragment
@DictionaryType({
@DictionaryAttribute(name = "port", optional = false, fragmentDependant = true),
@DictionaryAttribute(name = "maximum_size_messaging", defaultValue = "50", optional = false),
@DictionaryAttribute(name = "timer", defaultValue = "2000", optional = false),
@DictionaryAttribute(name = "replay", defaultValue = "true", optional = false, vals = {"true", "false"})
}
)
public class SocketChannel extends AbstractChannelFragment implements Runnable {
private ServerSocket server = null;
private List<Socket> localServerSockets = new ArrayList<Socket>();
/* thread in charge for receiving messages PRODUCTEUR */
private Thread reception_messages = null;
private DeadMessageQueueThread sending_messages_node_dead;
private boolean alive = false;
/* Current ID of the message */
private HashMap<String, Integer> fragments = null;
final Semaphore sem = new java.util.concurrent.Semaphore(1);
private Logger logger = LoggerFactory.getLogger(SocketChannel.class);
public Map<String, Socket> getClientSockets() {
return clientSockets;
}
private Map<String, Socket> clientSockets = new HashMap<String, Socket>();
private ChannelClassResolver resolver = new ChannelClassResolver(this);
@Override
public Object dispatch(Message message) {
for (org.kevoree.framework.KevoreePort p : getBindedPorts()) {
forward(p, message);
}
for (KevoreeChannelFragment cf : getOtherFragments()) {
if (!message.getPassedNodes().contains(cf.getNodeName())) {
forward(cf, message);
}
}
return null;
}
public String getAddress(String remoteNodeName) {
String ip = KevoreePlatformHelper.getProperty(this.getModelService().getLastModel(), remoteNodeName,
org.kevoree.framework.Constants.KEVOREE_PLATFORM_REMOTE_NODE_IP());
if (ip == null || ip.equals("")) {
ip = "127.0.0.1";
}
return ip;
}
public int parsePortNumber(String nodeName) throws IOException {
try {
//logger.debug("look for port on " + nodeName);
return KevoreeFragmentPropertyHelper
.getIntPropertyFromFragmentChannel(this.getModelService().getLastModel(), this.getName(), "port",
nodeName);
} catch (NumberFormatException e) {
throw new IOException(e.getMessage());
}
}
@Start
public void startChannel() {
logger.debug("Socket channel is starting ");
Integer maximum_size_messaging = Integer.parseInt(getDictionary().get("maximum_size_messaging").toString());
Integer timer = Integer.parseInt(getDictionary().get("timer").toString());
sending_messages_node_dead = new DeadMessageQueueThread(this,timer,maximum_size_messaging);
reception_messages = new Thread(this);
alive = true;
reception_messages.start();
sending_messages_node_dead.start();
fragments = new HashMap<String, Integer>();
}
@Stop
public void stopChannel() {
sending_messages_node_dead.stopProcess();
this.alive = false;
logger.debug("Socket channel is closing ");
try {
reception_messages.interrupt();
sending_messages_node_dead.interrupt();
} catch (Exception e) {
//logger.error(""+e);
}
for (Socket socket : clientSockets.values()) {
try {
socket.close();
} catch (IOException e) {
//logger.error("Error while trying to close socket", e);
}
}
for (Socket socket : localServerSockets) {
try {
socket.close();
} catch (IOException e) {
// logger.error("Error while trying to close socket", e);
}
}
// clean cache sockets
clientSockets.clear();
localServerSockets.clear();
logger.debug("Socket channel is closed ");
}
@Update
public void updateChannel() {
stopChannel();
startChannel(); // TODO CHECK MSG IN QUEUE
}
@Override
public ChannelFragmentSender createSender(final String remoteNodeName, String remoteChannelName) {
return new ChannelFragmentSender() {
@Override
public Object sendMessageToRemote(Message msg) {
int port=0;
String host="";
try {
sem.acquire();
} catch (InterruptedException e) {
// ignore
}
try {
logger.debug("Sending message to " + remoteNodeName);
msg.setDestNodeName(remoteNodeName);
host = getAddress(msg.getDestNodeName());
port = parsePortNumber(msg.getDestNodeName());
// adding the current node to passedNodes
if (!msg.getPassedNodes().contains(getNodeName())) {
msg.getPassedNodes().add(getNodeName());
}
// create the link if not exist
Socket client_consumer = getOrCreateSocket(host,port);
OutputStream os = client_consumer.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(msg);
oos.flush();
client_consumer = null;
os = null;
oos = null;
} catch (Exception e) {
logger.warn("Unable to send message to " + msg.getDestNodeName()+e);
delete_link(host,port);
if(getDictionary().get("replay").toString().equals("true"))
sending_messages_node_dead.addToDeadQueue(msg);
}
finally
{
msg = null;
sem.release();
}
return null;
}
};
}
public void nodeDown(Socket client){
logger.warn("Node is down ");
localServerSockets.remove(client);
try
{
client.close();
} catch (IOException e) {
}
}
@Override
public void run() {
int port;
try {
port = parsePortNumber(getNodeName());
logger.debug("Running Socket server <" + getNodeName() + "> port <" + port + ">");
server = new ServerSocket(port);
server.setSoTimeout(2000);
server.setReuseAddress(true);
} catch (IOException e) {
logger.error("Unable to create ServerSocket", e);
}
int maxConcurrentClients = 50;
final Semaphore sem = new Semaphore(maxConcurrentClients);
Executor pool = Executors.newFixedThreadPool(50);
while (alive) {
try {
sem.acquire();
} catch (InterruptedException e) {
continue;
}
final Socket client;
final InputStream stream;
try {
client = server.accept();
localServerSockets.add(client);
stream = client.getInputStream();
} catch (Exception e) {
if (alive) {
// logger.warn("Failed to accept client or get its input stream", e);
}
continue;
}
pool.execute(new Runnable() {
Message msg = null;
boolean _alive = true;
@Override
public void run() {
while (_alive) {
try {
if (stream != null) {
ObjectInputStream ois =null;
try {
ois = new ObjectInputStream(stream){
@Override
protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
Class c = null;
try {
if(c == null){
c = resolver.resolve(objectStreamClass.getName());
}
} catch (Exception e) { }
try {
if(c == null){
c = super.resolveClass(objectStreamClass);
}
} catch (Exception e) { }
try {
if(c == null){
c= Class.forName(objectStreamClass.getName());
}
} catch (Exception e) { }
return c;
}
};
msg = (Message) ois.readObject();
} catch (Exception e) {
ois = null;
nodeD own(client);
_alive =false;
msg = null;
}
} else {
// the remote node close the channel (update, down )
nodeDown(client);
_alive =false;
msg = null;
}
if (msg != null) {
logger.debug("Reading message from " + msg.getDestNodeName());
if (!msg.getPassedNodes().contains(getNodeName())) {
msg.getPassedNodes().add(getNodeName());
}
// remove duplicate message
if (getOtherFragments().size() > 1) {
int val=1;
if (fragments.containsKey(msg.getUuid().toString()))
{
val = fragments.get(msg.getUuid().toString());
val = val +1;
}else {
val =1;
remoteDispatch(msg);
}
// save
fragments.put(msg.getUuid().toString(), val);
if (val == getOtherFragments().size())
{
fragments.remove(msg.getUuid());
}
} else {
// two nodes
remoteDispatch(msg);
}
}else
{
nodeDown(client);
_alive =false;
msg = null;
}
msg = null;
} finally {
sem.release();
}
}
try {
client.close();
} catch (Exception e) {
// Ignore
}
}
});
} // while
logger.debug("The ServerSocket pool is closing");
try {
if (server != null) {
server.close();
}
logger.debug("ServerSocket is closed");
} catch (IOException e) {
}
}
public void delete_link(String host,Integer port){
/// link is down
clientSockets.remove(host+port);
}
public Socket getOrCreateSocket(String host,Integer port) throws IOException {
Socket client_consumer = null;
if (clientSockets.containsKey(host+port)) {
// logger.debug("the link exist");
client_consumer = clientSockets.get(host+port);
} else {
// logger.debug("no link in cache");
client_consumer = new Socket(host, port);
client_consumer.setTcpNoDelay(true);
//When a TCP connection is closed the connection may remain in a timeout state for a period of time after the connection is closed (typically known as the TIME_WAIT state or 2MSL wait state). For applications using a well known socket address or port it may not be possible to bind a socket to the required SocketAddress if there is a connection in the timeout state involving the socket address or port.
client_consumer.setReuseAddress(true);
client_consumer.setSoTimeout(2000);
client_consumer.setKeepAlive(true);
clientSockets.put(host+port, client_consumer);
}
return client_consumer;
}
}