/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library 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 library 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 library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.runtime.gateway;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import lucee.commons.lang.ExceptionUtil;
import lucee.loader.engine.CFMLEngine;
import lucee.loader.engine.CFMLEngineFactory;
import lucee.runtime.exp.PageException;
import lucee.runtime.type.Struct;
import lucee.runtime.util.Cast;
import lucee.runtime.util.Creation;
public class SocketGateway implements Gateway {
private GatewayEngine engine;
private int port;
private String welcomeMessage="Welcome to the Lucee Socket Gateway";
private String id;
private CFMLEngine cfmlEngine;
private Cast caster;
private Creation creator;
private List<SocketServerThread> sockets=new ArrayList<SocketServerThread>();
private ServerSocket serverSocket;
protected int state=STOPPED;
private String cfcPath;
@Override
public void init(GatewayEngine engine, String id, String cfcPath, Map config)throws GatewayException {
this.engine=engine;
cfmlEngine=CFMLEngineFactory.getInstance();
caster=cfmlEngine.getCastUtil();
creator = cfmlEngine.getCreationUtil();
this.cfcPath=cfcPath;
this.id=id;
// config
Object oPort=config.get("port");
port=caster.toIntValue(oPort, 1225);
Object oWM=config.get("welcomeMessage");
String strWM=caster.toString(oWM,"").trim();
if(strWM.length()>0)welcomeMessage=strWM;
}
@Override
public void doStart() {
state = STARTING;
try {
createServerSocket();
state = RUNNING;
do {
try {
SocketServerThread sst = new SocketServerThread(serverSocket.accept());
sst.start();
sockets.add(sst);
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
error("Failed to listen on Socket ["+id+"] on port ["+port+"]: " + t.getMessage());
}
}
while (getState()==RUNNING || getState()==STARTING);
close(serverSocket);
serverSocket = null;
}
catch (Throwable e) {
ExceptionUtil.rethrowIfNecessary(e);
state=FAILED;
error("Error in Socet Gateway ["+id+"]: " + e.getMessage());
e.printStackTrace();
//throw CFMLEngineFactory.getInstance().getCastUtil().toPageException(e);
}
}
@Override
public void doStop() {
state = STOPPING;
try{
// close all open connections
Iterator<SocketServerThread> it = sockets.iterator();
while (it.hasNext()) {
close(it.next().socket);
}
// close server socket
close(serverSocket);
serverSocket = null;
state = STOPPED;
}
catch(Throwable t){
ExceptionUtil.rethrowIfNecessary(t);
state=FAILED;
error("Error in Socket Gateway ["+id+"]: " + t.getMessage());
//throw CFMLEngineFactory.getInstance().getCastUtil().toPageException(e);
}
}
private void createServerSocket() throws PageException, RuntimeException {
try {
serverSocket = new ServerSocket(port);
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
error("Failed to start Socket Gateway ["+id+"] on port ["+port+"] " +t.getMessage());
throw CFMLEngineFactory.getInstance().getCastUtil().toPageException(t);
}
}
private void invokeListener(String line, String originatorID) {
Struct data=creator.createStruct();
data.setEL(creator.createKey("message"), line);
Struct event=creator.createStruct();
event.setEL(creator.createKey("data"), data);
event.setEL(creator.createKey("originatorID"), originatorID);
event.setEL(creator.createKey("cfcMethod"), "onIncomingMessage");
event.setEL(creator.createKey("cfcTimeout"), new Double(10));
event.setEL(creator.createKey("cfcPath"), cfcPath);
event.setEL(creator.createKey("gatewayType"), "Socket");
event.setEL(creator.createKey("gatewayId"), id);
if (engine.invokeListener(this, "onIncomingMessage", event))
info("Socket Gateway Listener ["+id+"] invoked.");
else
error("Failed to call Socket Gateway Listener ["+id+"]");
}
private class SocketServerThread extends Thread {
private Socket socket;
private PrintWriter out;
private String _id;
public SocketServerThread(Socket socket) throws IOException {
this.socket = socket;
out = new PrintWriter(socket.getOutputStream(), true);
this._id=String.valueOf(hashCode());
}
@Override
public void run() {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println(welcomeMessage);
out.print("> ");
String line;
while ((line = in.readLine()) != null) {
if (line.trim().equals("exit")) break;
invokeListener(line,_id);
}
//socketRegistry.remove(this.getName());
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
error("Failed to read from Socket Gateway ["+id+"]: " + t.getMessage());
}
finally{
close(out);
out=null;
close(in);
close(socket);
sockets.remove(this);
}
}
public void writeOutput(String str) {
out.println(str);
out.print("> ");
}
}
@Override
public String sendMessage(Map _data) {
Struct data=caster.toStruct(_data, null, false);
String msg = (String) data.get("message",null);
String originatorID=(String) data.get("originatorID",null);
String status="OK";
if (msg!=null ) {
Iterator<SocketServerThread> it = sockets.iterator();
SocketServerThread sst;
try {
boolean hasSend=false;
while(it.hasNext()){
sst=it.next();
if(originatorID!=null && !sst._id.equalsIgnoreCase(originatorID)) continue;
sst.writeOutput(msg);
hasSend=true;
}
if(!hasSend) {
if(sockets.size()==0) {
error("There is no connection");
status = "EXCEPTION";
}
else {
it = sockets.iterator();
StringBuilder sb=new StringBuilder();
while(it.hasNext()){
if(sb.length()>0) sb.append(", ");
sb.append(it.next()._id);
}
error("There is no connection with originatorID ["+originatorID+"], available originatorIDs are ["+sb+"]");
status = "EXCEPTION";
}
}
}
catch (Exception e) {
e.printStackTrace();
error("Failed to send message with exception: " + e.toString());
status = "EXCEPTION";
}
}
return status;
}
@Override
public void doRestart() {
doStop();
doStart();
}
@Override
public String getId() {
return id;
}
@Override
public int getState() {
return state;
}
@Override
public Object getHelper() {
return null;
}
public void info(String msg) {
engine.log(this,GatewayEngine.LOGLEVEL_INFO,msg);
}
public void error(String msg) {
engine.log(this,GatewayEngine.LOGLEVEL_ERROR,msg);
}
private void close(Writer writer) {
if(writer==null) return;
try{
writer.close();
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
}
private void close(Reader reader) {
if(reader==null) return;
try{
reader.close();
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
}
private void close(Socket socket) {
if(socket==null) return;
try{
socket.close();
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
}
private void close(ServerSocket socket) {
if(socket==null) return;
try{
socket.close();
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
}
}