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.IOException;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.Map.Entry;
026
027import javax.servlet.http.HttpServletRequest;
028
029import lucee.commons.io.DevNullOutputStream;
030import lucee.commons.io.log.Log;
031import lucee.commons.io.log.LogUtil;
032import lucee.commons.lang.ClassException;
033import lucee.commons.lang.ExceptionUtil;
034import lucee.commons.lang.Md5;
035import lucee.commons.lang.Pair;
036import lucee.loader.util.Util;
037import lucee.runtime.CFMLFactory;
038import lucee.runtime.Component;
039import lucee.runtime.ComponentPage;
040import lucee.runtime.PageContext;
041import lucee.runtime.PageContextImpl;
042import lucee.runtime.config.Config;
043import lucee.runtime.config.ConfigWeb;
044import lucee.runtime.config.ConfigWebImpl;
045import lucee.runtime.engine.ThreadLocalPageContext;
046import lucee.runtime.exp.ExpressionException;
047import lucee.runtime.exp.PageException;
048import lucee.runtime.exp.PageRuntimeException;
049import lucee.runtime.op.Caster;
050import lucee.runtime.thread.ThreadUtil;
051import lucee.runtime.type.Collection;
052import lucee.runtime.type.KeyImpl;
053import lucee.runtime.type.Struct;
054import lucee.runtime.type.StructImpl;
055import lucee.runtime.type.util.KeyConstants;
056
057public class GatewayEngineImpl implements GatewayEnginePro {
058
059        private static final Object OBJ = new Object();
060
061        private static final Collection.Key AMF_FORWARD = KeyImpl.init("AMF-Forward");
062
063        private Map<String,GatewayEntry> entries=new HashMap<String,GatewayEntry>();
064        private ConfigWeb config;
065        private Log log;
066
067        
068        public GatewayEngineImpl(ConfigWeb config){
069                this.config=config;
070                this.log=((ConfigWebImpl)config).getLog("gateway");
071                
072        }
073        
074        public void addEntries(Config config,Map<String, GatewayEntry> entries) throws ClassException, PageException,IOException {
075                Iterator<Entry<String, GatewayEntry>> it = entries.entrySet().iterator();
076                while(it.hasNext()){
077                        addEntry(config,it.next().getValue());
078                }
079        }
080
081        public void addEntry(Config config,GatewayEntry ge) throws ClassException, PageException,IOException {
082                String id=ge.getId().toLowerCase().trim();
083                GatewayEntry existing=entries.get(id);
084                GatewayPro g=null;
085                
086                // does not exist
087                if(existing==null) {
088                        entries.put(id,load(config,ge));
089                }
090                // exist but changed
091                else if(!existing.equals(ge)){
092                        g=existing.getGateway();
093                        if(g.getState()==GatewayPro.RUNNING) g.doStop();
094                        entries.put(id,load(config,ge));
095                }
096                // not changed
097                //else print.out("untouched:"+id);
098        }
099
100        private GatewayEntry load(Config config,GatewayEntry ge) throws ClassException,PageException {
101                ge.createGateway(config);
102                return ge;
103        }
104
105        /**
106         * @return the entries
107         */
108        public Map<String,GatewayEntry> getEntries() {
109                return entries;
110        }
111
112        public void remove(GatewayEntry ge) {
113                String id=ge.getId().toLowerCase().trim();
114                GatewayEntry existing=entries.remove(id);
115                GatewayPro g=null;
116                
117                // does not exist
118                if(existing!=null) {
119                        g=existing.getGateway();
120                        try{
121                                if(g.getState()==GatewayPro.RUNNING) g.doStop();
122                        }
123                        catch(Throwable t){
124                                ExceptionUtil.rethrowIfNecessary(t);
125                        }
126                }
127        }
128
129        /**
130         * get the state of gateway
131         * @param gatewayId
132         * @return
133         * @throws PageException
134         */
135        public int getState(String gatewayId) throws PageException {
136                return getGateway(gatewayId).getState();
137        }
138        
139        /**
140         * get helper object
141         * @param gatewayId
142         * @return
143         * @throws PageException
144         */
145        public Object getHelper(String gatewayId) throws PageException {
146                return getGateway(gatewayId).getHelper();
147        }
148
149        /**
150         * send the message to the gateway
151         * @param gatewayId
152         * @param data
153         * @return
154         * @throws PageException
155         */
156        public String sendMessage(String gatewayId, Struct data) throws PageException,IOException {
157                GatewayPro g = getGateway(gatewayId);
158                if(g.getState()!=GatewayPro.RUNNING) throw new GatewayException("Gateway ["+gatewayId+"] is not running");
159                return g.sendMessage(data);
160        }
161        
162        /**
163         * start the gateway
164         * @param gatewayId
165         * @throws PageException
166         */
167        public void start(String gatewayId) throws PageException {
168                executeThread(gatewayId,GatewayThread.START);
169        }
170        private void start(GatewayPro gateway) {
171                executeThread(gateway,GatewayThread.START);
172        }
173
174        /**
175         * stop the gateway
176         * @param gatewayId
177         * @throws PageException
178         */
179        public void stop(String gatewayId) throws PageException {
180                executeThread(gatewayId,GatewayThread.STOP);
181        }
182        private void stop(GatewayPro gateway) {
183                executeThread(gateway,GatewayThread.STOP);
184        }
185        
186        
187
188
189        public void reset() {
190                Iterator<Entry<String, GatewayEntry>> it = entries.entrySet().iterator();
191                Entry<String, GatewayEntry> entry;
192                GatewayEntry ge;
193                GatewayPro g;
194                while(it.hasNext()){
195                        entry = it.next();
196                        ge = entry.getValue();
197                        g=ge.getGateway();
198                        if(g.getState()==GatewayPro.RUNNING) {
199                                try {
200                                        g.doStop();
201                                } catch (IOException e) {
202                                        log(g, LOGLEVEL_ERROR, e.getMessage());
203                                }
204                        }
205                        if(ge.getStartupMode()==GatewayEntry.STARTUP_MODE_AUTOMATIC)
206                                start(g);
207                        
208                }
209        }
210
211        public synchronized void clear() {
212                Iterator<Entry<String, GatewayEntry>> it = entries.entrySet().iterator();
213                Entry<String, GatewayEntry> entry;
214                while(it.hasNext()){
215                        entry = it.next();
216                        if(entry.getValue().getGateway().getState()==GatewayPro.RUNNING) 
217                                stop(entry.getValue().getGateway());
218                }
219                entries.clear();
220        }
221        
222        /**
223         * restart the gateway
224         * @param gatewayId
225         * @throws PageException
226         */
227        public void restart(String gatewayId) throws PageException {
228                executeThread(gatewayId,GatewayThread.RESTART);
229        }
230        
231        private GatewayPro getGateway(String gatewayId) throws PageException {
232                return getGatewayEntry(gatewayId).getGateway();
233        }
234        
235        private GatewayEntry getGatewayEntry(String gatewayId) throws PageException {
236                String id=gatewayId.toLowerCase().trim();
237                GatewayEntry ge=entries.get(id);
238                if(ge!=null) return ge;
239                
240                // create list
241                Iterator<String> it = entries.keySet().iterator();
242                StringBuilder sb=new StringBuilder();
243                while(it.hasNext()){
244                        if(sb.length()>0) sb.append(", ");
245                        sb.append(it.next());
246                }
247                
248                throw new ExpressionException("there is no gateway instance with id ["+gatewayId+"], available gateway instances are ["+sb+"]");
249        }
250        private GatewayEntry getGatewayEntry(GatewayPro gateway)  {
251                String gatewayId=gateway.getId();
252                // it must exist, because it only can come from here
253                return entries.get(gatewayId);
254        }
255        
256        private void executeThread(String gatewayId, int action) throws PageException {
257                new GatewayThread(this,getGateway(gatewayId),action).start();
258        }
259
260        private void executeThread(GatewayPro g, int action) {
261                new GatewayThread(this,g,action).start();
262        }
263        
264        
265        
266
267        public static int toIntState(String state, int defaultValue) {
268                state=state.trim().toLowerCase();
269                if("running".equals(state))     return GatewayPro.RUNNING;
270                if("started".equals(state)) return GatewayPro.RUNNING;
271                if("run".equals(state))         return GatewayPro.RUNNING;
272                
273                if("failed".equals(state))      return GatewayPro.FAILED;
274                if("starting".equals(state))return GatewayPro.STARTING;
275                if("stopped".equals(state)) return GatewayPro.STOPPED;
276                if("stopping".equals(state))return GatewayPro.STOPPING;
277                
278                return defaultValue;
279        }
280        
281        public static String toStringState(int state, String defaultValue) {
282                if(GatewayPro.RUNNING==state)   return "running";
283                if(GatewayPro.FAILED==state)    return "failed";
284                if(GatewayPro.STOPPED==state)   return "stopped";
285                if(GatewayPro.STOPPING==state)  return "stopping";
286                if(GatewayPro.STARTING==state)  return "starting";
287                        
288                return defaultValue;
289        }
290
291        public boolean invokeListener(GatewayPro gateway, String method, Map data) {// FUTUTE add generic type to interface
292                return invokeListener(gateway.getId(), method, data);
293        }
294
295        public boolean invokeListener(String gatewayId, String method, Map data) {// do not add this method to loade, it can be removed with Lucee 5
296                data=GatewayUtil.toCFML(data);
297                
298                GatewayEntry entry;
299                try {
300                        entry = getGatewayEntry(gatewayId);
301                }
302                catch (PageException pe) {
303                        throw new PageRuntimeException(pe);
304                }
305                String cfcPath = entry.getListenerCfcPath();
306                if(!Util.isEmpty(cfcPath,true)){
307                        try {
308                                if(!callOneWay(cfcPath,gatewayId, method, Caster.toStruct(data,null,false), false))
309                                        log(gatewayId,LOGLEVEL_ERROR, "function ["+method+"] does not exist in cfc ["+toRequestURI(cfcPath)+"]");
310                                else
311                                        return true;
312                        } 
313                        catch (PageException e) {
314                                e.printStackTrace();
315                                log(gatewayId,LOGLEVEL_ERROR, e.getMessage());
316                        }
317                }
318                else
319                        log(gatewayId,LOGLEVEL_ERROR, "there is no listener cfc defined");
320                return false;
321        }
322        
323        public Object callEL(String cfcPath,String id,String functionName,Struct arguments, boolean cfcPeristent, Object defaultValue)  {
324                try {
325                        return call(cfcPath,id,functionName, arguments,cfcPeristent, defaultValue);
326                } catch (PageException e) {
327                        return defaultValue;
328                }
329        }
330        
331        public boolean callOneWay(String cfcPath,String id,String functionName,Struct arguments, boolean cfcPeristent) throws PageException {
332                return call(cfcPath,id,functionName, arguments,cfcPeristent, OBJ)!=OBJ;
333        }
334
335        public Object getComponent(String cfcPath,String id) throws PageException  {
336                String requestURI=toRequestURI(cfcPath);
337                
338                PageContext oldPC = ThreadLocalPageContext.get();
339                PageContextImpl pc = createPageContext(requestURI,id, "init", null, false);
340                try {
341                        ThreadLocalPageContext.register(pc);
342                        return getCFC(pc,requestURI);
343                }
344                finally{
345                        CFMLFactory f = config.getFactory();
346                        f.releasePageContext(pc);
347                        ThreadLocalPageContext.register(oldPC);
348                }
349        }
350
351        public Object call(String cfcPath,String id,String functionName,Struct arguments, boolean cfcPeristent, Object defaultValue) throws PageException  {
352                String requestURI=toRequestURI(cfcPath);
353                
354                PageContext oldPC = ThreadLocalPageContext.get();
355                PageContextImpl pc=createPageContext(requestURI,id,functionName,arguments,cfcPeristent);
356                
357                try {
358                        ThreadLocalPageContext.register(pc);
359                        Component cfc=getCFC(pc,requestURI);
360                        if(cfc.containsKey(functionName)){
361                                pc.execute(requestURI, true,false);
362                                // Result
363                                return pc.variablesScope().get(AMF_FORWARD,null);
364                        }
365                }
366                finally{
367                        CFMLFactory f = config.getFactory();
368                        f.releasePageContext(pc);
369                        ThreadLocalPageContext.register(oldPC);
370                }
371                return defaultValue;
372        }
373
374        private Component getCFC(PageContextImpl pc,String requestURI) throws PageException  {
375                HttpServletRequest req = pc.getHttpServletRequest();
376                try {
377                        req.setAttribute("client", "lucee-gateway-1-0");
378                        req.setAttribute("call-type", "store-only");
379                        pc.execute(requestURI, true,false);
380                        return (Component) req.getAttribute("component");
381                }
382                finally {
383                        req.removeAttribute("call-type");
384                        req.removeAttribute("component");
385                }
386        }
387        
388        private PageContextImpl createPageContext(String requestURI,String id,String functionName, Struct arguments, boolean cfcPeristent) throws PageException {
389                Struct attrs=new StructImpl();
390                String remotePersisId;
391                try {
392                        remotePersisId=Md5.getDigestAsString(requestURI+id);
393                } catch (IOException e) {
394                        throw Caster.toPageException(e);
395                }
396                PageContextImpl pc = ThreadUtil.createPageContext(
397                                config, 
398                                DevNullOutputStream.DEV_NULL_OUTPUT_STREAM, 
399                                "localhost", 
400                                requestURI, 
401                                "method="+functionName+(cfcPeristent?"&"+ComponentPage.REMOTE_PERSISTENT_ID+"="+remotePersisId:""), 
402                                null, 
403                                new Pair[]{new Pair<String,Object>("AMF-Forward","true")}, 
404                                null, 
405                                attrs);
406                
407                pc.setRequestTimeout(999999999999999999L); 
408                pc.setGatewayContext(true);
409                if(arguments!=null)attrs.setEL(KeyConstants._argumentCollection, arguments);
410                attrs.setEL("client", "lucee-gateway-1-0");
411                return pc;
412        }
413
414        public String toRequestURI(String cfcPath) {
415                return GatewayUtil.toRequestURI(cfcPath);
416        }
417
418        @Override
419        public void log(GatewayPro gateway, int level, String message) {
420                log(gateway.getId(), level, message);
421        }
422        
423        public void log(String gatewayId, int level, String message) {
424                int l=level;
425                switch(level){
426                case LOGLEVEL_INFO:l=Log.LEVEL_INFO;
427                break;
428                case LOGLEVEL_DEBUG:l=Log.LEVEL_DEBUG;
429                break;
430                case LOGLEVEL_ERROR:l=Log.LEVEL_ERROR;
431                break;
432                case LOGLEVEL_FATAL:l=Log.LEVEL_FATAL;
433                break;
434                case LOGLEVEL_WARN:l=Log.LEVEL_WARN;
435                break;
436                case LOGLEVEL_TRACE:l=LogUtil.LEVEL_TRACE;
437                break;
438                }
439                log.log(l, "Gateway:"+gatewayId, message);
440        }
441        
442
443        private Map<String, Component> persistentRemoteCFC;
444        public Component getPersistentRemoteCFC(String id) {
445                if(persistentRemoteCFC==null) persistentRemoteCFC=new HashMap<String,Component>();
446                return persistentRemoteCFC.get(id);
447        }
448        
449        public Component setPersistentRemoteCFC(String id, Component cfc) {
450                if(persistentRemoteCFC==null) persistentRemoteCFC=new HashMap<String,Component>();
451                return persistentRemoteCFC.put(id,cfc);
452        }
453}
454