001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.gateway;
020
021import java.io.BufferedReader;
022import java.io.IOException;
023import java.io.InputStreamReader;
024import java.io.PrintWriter;
025import java.io.Reader;
026import java.io.Writer;
027import java.net.ServerSocket;
028import java.net.Socket;
029import java.util.ArrayList;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033
034import lucee.commons.lang.ExceptionUtil;
035import lucee.loader.engine.CFMLEngine;
036import lucee.loader.engine.CFMLEngineFactory;
037import lucee.runtime.exp.PageException;
038import lucee.runtime.type.Struct;
039import lucee.runtime.util.Cast;
040import lucee.runtime.util.Creation;
041
042public class SocketGateway implements GatewayPro {
043
044        private GatewayEnginePro engine;
045        private int port;
046        private String welcomeMessage="Welcome to the Lucee Socket Gateway";
047    
048        private String id;
049        private CFMLEngine cfmlEngine;
050        private Cast caster;
051        private Creation creator;
052        private List<SocketServerThread> sockets=new ArrayList<SocketServerThread>();
053        private ServerSocket serverSocket;
054        protected int state=STOPPED;
055        private String cfcPath;
056
057
058        @Override
059        public void init(GatewayEnginePro engine, String id, String cfcPath, Map config)throws GatewayException {
060                this.engine=engine;
061                cfmlEngine=CFMLEngineFactory.getInstance();
062                caster=cfmlEngine.getCastUtil();
063                creator = cfmlEngine.getCreationUtil();
064                this.cfcPath=cfcPath;
065                this.id=id;
066                
067                // config
068                Object oPort=config.get("port");
069                port=caster.toIntValue(oPort, 1225);
070
071                Object oWM=config.get("welcomeMessage");
072                String strWM=caster.toString(oWM,"").trim();
073                if(strWM.length()>0)welcomeMessage=strWM;
074        }
075        
076        
077        public void doStart() {
078                state = STARTING;
079                try     {
080                        createServerSocket();
081                        state = RUNNING;
082                        do {
083                        try {
084                                SocketServerThread sst = new SocketServerThread(serverSocket.accept());
085                    sst.start();
086                    sockets.add(sst);
087                }
088                catch (Throwable t) {
089                                ExceptionUtil.rethrowIfNecessary(t);
090                    error("Failed to listen on Socket ["+id+"] on port ["+port+"]: " + t.getMessage());
091                }
092            }
093                        while (getState()==RUNNING || getState()==STARTING);
094                        
095            close(serverSocket);
096            serverSocket = null;
097        }
098        catch (Throwable e) {
099                        ExceptionUtil.rethrowIfNecessary(e);
100                state=FAILED;
101                    error("Error in Socet Gateway ["+id+"]: " + e.getMessage());
102            e.printStackTrace();
103            //throw CFMLEngineFactory.getInstance().getCastUtil().toPageException(e);
104        }
105    }
106        
107        public void doStop()    {
108                state = STOPPING;
109                try{
110                        
111                        // close all open connections
112                        Iterator<SocketServerThread> it = sockets.iterator();
113                while (it.hasNext()) {
114                    close(it.next().socket);
115                }
116                
117                // close server socket
118                close(serverSocket);
119                serverSocket = null;
120                    state = STOPPED;
121                }
122                catch(Throwable e){
123                        ExceptionUtil.rethrowIfNecessary(e);
124                        state=FAILED;
125                        error("Error in Socket Gateway ["+id+"]: " + e.getMessage());
126            e.printStackTrace();
127            //throw CFMLEngineFactory.getInstance().getCastUtil().toPageException(e);
128                }
129    }
130
131        private void createServerSocket() throws PageException, RuntimeException {
132                try     {
133            serverSocket = new ServerSocket(port);
134        }
135        catch (Throwable t) {
136                        ExceptionUtil.rethrowIfNecessary(t);
137            error("Failed to start Socket Gateway ["+id+"] on port ["+port+"] " +t.getMessage());
138            throw CFMLEngineFactory.getInstance().getCastUtil().toPageException(t);
139        }
140        }
141         
142         
143         
144
145         private void invokeListener(String line, String originatorID) {
146                
147                Struct data=creator.createStruct();
148        data.setEL(creator.createKey("message"), line);
149        Struct event=creator.createStruct();
150        event.setEL(creator.createKey("data"), data);
151        event.setEL(creator.createKey("originatorID"), originatorID);
152        
153        event.setEL(creator.createKey("cfcMethod"), "onIncomingMessage");
154        event.setEL(creator.createKey("cfcTimeout"), new Double(10));
155        event.setEL(creator.createKey("cfcPath"), cfcPath);
156
157        event.setEL(creator.createKey("gatewayType"), "Socket");
158        event.setEL(creator.createKey("gatewayId"), id);
159        
160        
161                            
162        if (engine.invokeListener(this, "onIncomingMessage", event))    
163            info("Socket Gateway Listener ["+id+"] invoked.");
164        else
165                error("Failed to call Socket Gateway Listener ["+id+"]");           
166         }
167
168         
169         private class SocketServerThread extends Thread        {
170                private Socket socket;
171                private PrintWriter out;
172                        private String _id;
173                        
174                public SocketServerThread(Socket socket) throws IOException     {
175                    this.socket = socket;
176                out = new PrintWriter(socket.getOutputStream(), true);
177                this._id=String.valueOf(hashCode());
178                }
179
180                public void run()       {
181                        BufferedReader in = null;
182                    try {
183                        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
184                        out.println(welcomeMessage);
185                        out.print("> ");
186                        String line;
187                        while ((line = in.readLine()) != null) {
188                                if (line.trim().equals("exit")) break;
189                            invokeListener(line,_id);
190                        }
191                        //socketRegistry.remove(this.getName());
192                    }
193                    catch (Throwable t) {
194                                ExceptionUtil.rethrowIfNecessary(t);
195                        error("Failed to read from Socket Gateway ["+id+"]: " + t.getMessage());
196                    }
197                    finally{
198                        close(out);
199                        out=null;
200                        close(in);
201                        close(socket);
202                        sockets.remove(this);
203                    }
204                }
205
206                        public void writeOutput(String str)     {
207                                out.println(str);
208                    out.print("> ");
209                }
210            }
211
212        
213        
214        
215        
216
217
218
219        public String sendMessage(Map _data) {
220                Struct data=caster.toStruct(_data, null, false);
221                String msg = (String) data.get("message",null);
222                String originatorID=(String) data.get("originatorID",null);
223                
224                String status="OK";
225        if (msg!=null ) {
226            
227                Iterator<SocketServerThread> it = sockets.iterator();
228                SocketServerThread sst;
229                try     {
230                        boolean hasSend=false;
231                        while(it.hasNext()){
232                                sst=it.next();
233                                if(originatorID!=null && !sst._id.equalsIgnoreCase(originatorID)) continue;
234                                sst.writeOutput(msg);
235                                hasSend=true;
236                        }
237                        
238                        if(!hasSend) {
239                                if(sockets.size()==0) {
240                                        error("There is no connection");
241                                status = "EXCEPTION";
242                                }
243                                else {
244                                        it = sockets.iterator();
245                                        StringBuilder sb=new StringBuilder();
246                                        while(it.hasNext()){
247                                                if(sb.length()>0) sb.append(", ");
248                                                sb.append(it.next()._id);
249                                        }
250                                        error("There is no connection with originatorID ["+originatorID+"], available originatorIDs are ["+sb+"]");
251                                        status = "EXCEPTION";
252                                }
253                        }
254            }
255            catch (Exception e) {
256                e.printStackTrace();
257                error("Failed to send message with exception: " + e.toString());
258                status = "EXCEPTION";
259            }
260        }
261        return status;
262    }
263        
264        
265        @Override
266        public void doRestart() {
267                doStop();
268                doStart();
269        }
270        
271        
272
273        @Override
274        public String getId() {
275                return id;
276        }
277        
278         @Override
279        public int getState() {
280                return state;
281         }
282        
283        
284
285    @Override
286    public Object getHelper() {
287        return null;
288    }
289
290
291        public void info(String msg) {
292                engine.log(this,GatewayEnginePro.LOGLEVEL_INFO,msg);
293        }
294        
295        public void error(String msg) {
296                engine.log(this,GatewayEnginePro.LOGLEVEL_ERROR,msg);
297        }
298            
299
300    private void close(Writer writer) {
301                if(writer==null) return;
302                try{
303                        writer.close();
304                }
305                catch(Throwable t){
306                        ExceptionUtil.rethrowIfNecessary(t);
307                }
308        }
309    private void close(Reader reader) {
310                if(reader==null) return;
311                try{
312                        reader.close();
313                }
314                catch(Throwable t){
315                        ExceptionUtil.rethrowIfNecessary(t);
316                }
317        }
318    private void close(Socket socket) {
319                if(socket==null) return;
320                try{
321                        socket.close();
322                }
323                catch(Throwable t){
324                        ExceptionUtil.rethrowIfNecessary(t);
325                }
326        }
327    private void close(ServerSocket socket) {
328                if(socket==null) return;
329                try{
330                        socket.close();
331                }
332                catch(Throwable t){
333                        ExceptionUtil.rethrowIfNecessary(t);
334                }
335        }
336
337}