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.listener;
020
021import java.io.IOException;
022import java.io.OutputStream;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028
029import javax.servlet.http.Cookie;
030
031import lucee.commons.io.DevNullOutputStream;
032import lucee.commons.io.res.Resource;
033import lucee.commons.io.res.util.ResourceUtil;
034import lucee.commons.lang.StringUtil;
035import lucee.commons.lang.mimetype.MimeType;
036import lucee.commons.lang.types.RefBoolean;
037import lucee.commons.lang.types.RefBooleanImpl;
038import lucee.runtime.CFMLFactory;
039import lucee.runtime.Component;
040import lucee.runtime.ComponentPage;
041import lucee.runtime.ComponentPro;
042import lucee.runtime.PageContext;
043import lucee.runtime.PageContextImpl;
044import lucee.runtime.PageSource;
045import lucee.runtime.component.ComponentLoader;
046import lucee.runtime.component.Member;
047import lucee.runtime.engine.ThreadLocalPageContext;
048import lucee.runtime.exp.Abort;
049import lucee.runtime.exp.MissingIncludeException;
050import lucee.runtime.exp.PageException;
051import lucee.runtime.exp.PostContentAbort;
052import lucee.runtime.interpreter.JSONExpressionInterpreter;
053import lucee.runtime.net.http.HttpServletRequestDummy;
054import lucee.runtime.net.http.HttpServletResponseDummy;
055import lucee.runtime.net.http.ReqRspUtil;
056import lucee.runtime.op.Caster;
057import lucee.runtime.op.Decision;
058import lucee.runtime.op.Duplicator;
059import lucee.runtime.orm.ORMUtil;
060import lucee.runtime.type.Array;
061import lucee.runtime.type.ArrayImpl;
062import lucee.runtime.type.Collection;
063import lucee.runtime.type.Collection.Key;
064import lucee.runtime.type.KeyImpl;
065import lucee.runtime.type.Struct;
066import lucee.runtime.type.scope.UndefinedImpl;
067import lucee.runtime.type.util.ArrayUtil;
068import lucee.runtime.type.util.KeyConstants;
069import lucee.runtime.type.util.UDFUtil;
070
071public class ModernAppListener extends AppListenerSupport {
072
073
074
075        
076
077        private static final Collection.Key ON_REQUEST_START = KeyImpl.intern("onRequestStart");
078        private static final Collection.Key ON_CFCREQUEST = KeyImpl.intern("onCFCRequest");
079        private static final Collection.Key ON_REQUEST = KeyImpl.intern("onRequest");
080        private static final Collection.Key ON_REQUEST_END = KeyImpl.intern("onRequestEnd");
081        private static final Collection.Key ON_ABORT = KeyImpl.intern("onAbort");
082        private static final Collection.Key ON_APPLICATION_START = KeyImpl.intern("onApplicationStart");
083        private static final Collection.Key ON_APPLICATION_END = KeyImpl.intern("onApplicationEnd");
084        private static final Collection.Key ON_SESSION_START = KeyImpl.intern("onSessionStart");
085        private static final Collection.Key ON_SESSION_END = KeyImpl.intern("onSessionEnd");
086        private static final Collection.Key ON_DEBUG = KeyImpl.intern("onDebug");
087        private static final Collection.Key ON_ERROR = KeyImpl.intern("onError");
088        private static final Collection.Key ON_MISSING_TEMPLATE = KeyImpl.intern("onMissingTemplate");
089        
090        
091        //private ComponentImpl app;
092        private Map<String,ComponentPro> apps=new HashMap<String,ComponentPro>();
093        protected int mode=MODE_CURRENT2ROOT;
094        
095        @Override
096        public void onRequest(PageContext pc, PageSource requestedPage, RequestListener rl) throws PageException {
097                // on requestStart
098                PageSource appPS=//pc.isCFCRequest()?null:
099                        AppListenerUtil.getApplicationPageSource(pc,requestedPage,mode,AppListenerUtil.TYPE_NEW,null);
100                
101                _onRequest(pc, requestedPage, appPS,rl);
102        }
103        
104        protected void _onRequest(PageContext pc, PageSource requestedPage,PageSource appPS, RequestListener rl) throws PageException {
105                PageContextImpl pci = (PageContextImpl)pc;
106                pci.setAppListenerType(AppListenerUtil.TYPE_NEW);
107                
108                if(appPS!=null) {
109                        String callPath=appPS.getComponentName();
110                        
111                        
112                        ComponentPro app = ComponentLoader.loadComponent(pci,null,appPS, callPath, false,false);
113                        
114                        // init
115                        initApplicationContext(pci,app);
116                
117                        
118                        apps.put(pc.getApplicationContext().getName(), app);
119
120                        if(!pci.initApplicationContext(this)) return;
121                        
122                        if(rl!=null) {
123                                requestedPage=rl.execute(pc, requestedPage);
124                                if(requestedPage==null) return;
125                        }
126                        
127                        String targetPage=requestedPage.getFullRealpath();
128                        RefBoolean goon=new RefBooleanImpl(true);
129                        
130                        // onRequestStart
131                        if(app.contains(pc,ON_REQUEST_START)) {
132                                try {
133                                        Object rtn=call(app,pci, ON_REQUEST_START, new Object[]{targetPage},false);
134                                        if(!Caster.toBooleanValue(rtn,true))
135                                                return;
136                                }
137                                catch(PageException pe){
138                                        pe=handlePageException(pci,app,pe,requestedPage,targetPage,goon);
139                                        if(pe!=null) throw pe;
140                                }
141                        }
142                
143                        // onRequest
144                        if(goon.toBooleanValue()) {
145                        boolean isCFC=ResourceUtil.getExtension(targetPage,"").equalsIgnoreCase(pc.getConfig().getCFCExtension());
146                        Object method;
147                        if(isCFC && app.contains(pc,ON_CFCREQUEST) && (method=pc.urlFormScope().get(KeyConstants._method,null))!=null) { 
148                                
149                                Struct url = (Struct)Duplicator.duplicate(pc.urlFormScope(),true);
150
151                                url.removeEL(KeyConstants._fieldnames);
152                                url.removeEL(KeyConstants._method);
153                                
154                                Object args=url.get(KeyConstants._argumentCollection,null);
155                                
156                                // url returnFormat
157                                Object oReturnFormat=url.removeEL(KeyConstants._returnFormat);
158                                int urlReturnFormat=-1;
159                                if(oReturnFormat!=null) urlReturnFormat=UDFUtil.toReturnFormat(Caster.toString(oReturnFormat,null),-1);
160                                
161                                // request header accept
162                                List<MimeType> accept = ReqRspUtil.getAccept(pc);
163                                int headerReturnFormat = MimeType.toFormat(accept, -1,-1);
164
165                        Object queryFormat=url.removeEL(KeyConstants._queryFormat);
166                        
167                        if(args==null){
168                                args=pc.getHttpServletRequest().getAttribute("argumentCollection");
169                        }
170                        
171                        if(args instanceof String){
172                                args=new JSONExpressionInterpreter().interpret(pc, (String)args);
173                        }
174                        
175                        if(args!=null) {
176                                if(Decision.isCastableToStruct(args)){
177                                        Struct sct = Caster.toStruct(args,false);
178                                        //Key[] keys = url.keys();
179                                        Iterator<Entry<Key, Object>> it = url.entryIterator();
180                                        Entry<Key, Object> e;
181                                        while(it.hasNext()){
182                                                e = it.next();
183                                                sct.setEL(e.getKey(),e.getValue());
184                                        }
185                                        args=sct;
186                                }
187                                else if(Decision.isCastableToArray(args)){
188                                        args = Caster.toArray(args);
189                                }
190                                else {
191                                        Array arr = new ArrayImpl();
192                                        arr.appendEL(args);
193                                        args=arr;
194                                }
195                        }
196                        else 
197                                args=url;
198
199                        Object rtn = call(app,pci, ON_CFCREQUEST, new Object[]{requestedPage.getComponentName(),method,args},true);
200                        
201                        if(rtn!=null){
202                                if(pc.getHttpServletRequest().getHeader("AMF-Forward")!=null) {
203                                        pc.variablesScope().setEL("AMF-Forward", rtn);
204                                        //ThreadLocalWDDXResult.set(rtn);
205                                }
206                                else {
207                                        try {
208                                                        ComponentPage.writeToResponseStream(pc,app,method.toString(),urlReturnFormat,headerReturnFormat,queryFormat,rtn);
209                                                } catch (Exception e) {
210                                                        throw Caster.toPageException(e);
211                                                }
212                                }
213                        }
214                                
215                                
216                        }
217                        //else if(!isCFC && app.contains(pc,ON_REQUEST)) {}
218                        else {
219                                // TODO impl die nicht so generisch ist
220                                try{
221
222                                        if(!isCFC && app.contains(pc,ON_REQUEST))
223                                                call(app,pci, ON_REQUEST, new Object[]{targetPage},false);
224                                        else
225                                                pci.doInclude(requestedPage);
226                                }
227                                catch(PageException pe){
228                                        pe=handlePageException(pci,app,pe,requestedPage,targetPage,goon);
229                                        if(pe!=null) throw pe;
230                                }
231                        }
232                        }
233                        // onRequestEnd
234                        if(goon.toBooleanValue() && app.contains(pc,ON_REQUEST_END)) {
235                                try {
236                                        call(app,pci, ON_REQUEST_END, new Object[]{targetPage},false);
237                                }
238                                catch(PageException pe){
239                                        pe=handlePageException(pci,app,pe,requestedPage,targetPage,goon);
240                                        if(pe!=null) throw pe;
241                                }
242                        }
243                }
244                else {
245                        apps.put(pc.getApplicationContext().getName(), null);
246                        pc.doInclude(requestedPage);
247                }
248        }
249        
250
251        private PageException handlePageException(PageContextImpl pci, ComponentPro app, PageException pe, PageSource requestedPage, String targetPage, RefBoolean goon) throws PageException {
252                PageException _pe=pe;
253                if(pe instanceof ModernAppListenerException) {
254                        _pe=((ModernAppListenerException) pe).getPageException();
255                }
256                
257                if(!Abort.isSilentAbort(_pe)) {
258                        if(_pe instanceof MissingIncludeException){
259                                if(((MissingIncludeException) _pe).getPageSource().equals(requestedPage)){
260                                        
261                                        if(app.contains(pci,ON_MISSING_TEMPLATE)) {
262                                                goon.setValue(false);
263                                                if(!Caster.toBooleanValue(call(app,pci, ON_MISSING_TEMPLATE, new Object[]{targetPage},true),true))
264                                                        return pe;
265                                        }
266                                        else return pe;
267                                }
268                                else return pe;
269                        }
270                        else return pe;
271                }
272                else {
273                        goon.setValue(false);
274                        if(app.contains(pci,ON_ABORT)) {
275                                call(app,pci, ON_ABORT, new Object[]{targetPage},true);
276                        }
277                }
278                return null;
279        }
280
281        @Override
282        public boolean onApplicationStart(PageContext pc) throws PageException {
283                ComponentPro app = apps.get(pc.getApplicationContext().getName());
284                if(app!=null && app.contains(pc,ON_APPLICATION_START)) {
285                        Object rtn = call(app,pc, ON_APPLICATION_START, ArrayUtil.OBJECT_EMPTY,true);
286                        return Caster.toBooleanValue(rtn,true);
287                }
288                return true;
289        }
290
291        @Override
292        public void onApplicationEnd(CFMLFactory factory, String applicationName) throws PageException {
293                ComponentPro app = apps.get(applicationName);
294                if(app==null || !app.containsKey(ON_APPLICATION_END)) return;
295                
296                PageContextImpl pc=(PageContextImpl) ThreadLocalPageContext.get();
297                boolean createPc=pc==null;
298                try {
299                        if(createPc)pc =  createPageContext(factory,app,applicationName,null,ON_APPLICATION_END);
300                        call(app,pc, ON_APPLICATION_END, new Object[]{pc.applicationScope()},true);
301                }
302                finally {
303                        if(createPc && pc!=null){
304                                factory.releasePageContext(pc);
305                        }
306                }
307        }
308        
309        @Override
310        public void onSessionStart(PageContext pc) throws PageException {
311                ComponentPro app = apps.get(pc.getApplicationContext().getName());
312                if(hasOnSessionStart(pc,app)) {
313                        call(app,pc, ON_SESSION_START, ArrayUtil.OBJECT_EMPTY,true);
314                }
315        }
316        
317        @Override
318        public boolean hasOnSessionEnd(String applicationName) {
319                ComponentPro app = apps.get(applicationName);
320                return app!=null && app.containsKey(ON_SESSION_END);
321        }
322        
323        @Override
324        public void onSessionEnd(CFMLFactory factory, String applicationName, String cfid) throws PageException {
325                ComponentPro app = apps.get(applicationName);
326                if(app==null || !app.containsKey(ON_SESSION_END)) return;
327                
328                PageContextImpl pc=null;
329                try {
330                        pc = createPageContext(factory,app,applicationName,cfid,ON_SESSION_END);
331                        call(app,pc, ON_SESSION_END, new Object[]{pc.sessionScope(false),pc.applicationScope()},true);
332                }
333                finally {
334                        if(pc!=null){
335                                factory.releasePageContext(pc);
336                        }
337                }
338        }
339
340        private PageContextImpl createPageContext(CFMLFactory factory, ComponentPro app, String applicationName, String cfid,Collection.Key methodName) throws PageException {
341                Resource root = factory.getConfig().getRootDirectory();
342                String path = app.getPageSource().getFullRealpath();
343                
344                // Request
345                HttpServletRequestDummy req = new HttpServletRequestDummy(root,"localhost",path,"",null,null,null,null,null);
346                if(!StringUtil.isEmpty(cfid))req.setCookies(new Cookie[]{new Cookie("cfid",cfid),new Cookie("cftoken","0")});
347                
348                // Response     
349                OutputStream os=DevNullOutputStream.DEV_NULL_OUTPUT_STREAM;
350                
351                // File based output stream
352                /*try {
353                        Resource out = factory.getConfig().getConfigDir().getRealResource("output/"+methodName.getString()+".out");
354                        out.getParentResource().mkdirs();
355                        os = out.getOutputStream(false);
356                } 
357                catch (IOException e) {}*/
358                
359                
360                HttpServletResponseDummy rsp = new HttpServletResponseDummy(os);
361                
362                // PageContext
363                PageContextImpl pc = (PageContextImpl) factory.getLuceePageContext(factory.getServlet(), req, rsp, null, false, -1, false);
364                // ApplicationContext
365                ClassicApplicationContext ap = new ClassicApplicationContext(factory.getConfig(),applicationName,false,app==null?null:ResourceUtil.getResource(pc,app.getPageSource(),null));
366                initApplicationContext(pc, app);
367                ap.setName(applicationName);
368                ap.setSetSessionManagement(true);
369                //if(!ap.hasName())ap.setName("Controler")
370                // Base
371                pc.setBase(app.getPageSource());
372                
373                return pc;
374        }
375
376        @Override
377        public void onDebug(PageContext pc) throws PageException {
378                if(((PageContextImpl)pc).isGatewayContext() || !pc.getConfig().debug()) return;
379                ComponentPro app = apps.get(pc.getApplicationContext().getName());
380                if(app!=null && app.contains(pc,ON_DEBUG)) {
381                        call(app,pc, ON_DEBUG, new Object[]{pc.getDebugger().getDebuggingData(pc)},true);
382                        return;
383                }
384                try {
385                        pc.getDebugger().writeOut(pc);
386                } 
387                catch (IOException e) {
388                        throw Caster.toPageException(e);
389                }
390        }
391
392        @Override
393        public void onError(PageContext pc, PageException pe) {
394                ComponentPro app =  apps.get(pc.getApplicationContext().getName());
395                if(app!=null && app.containsKey(ON_ERROR) && !Abort.isSilentAbort(pe)) {
396                        try {
397                                String eventName="";
398                                if(pe instanceof ModernAppListenerException) eventName= ((ModernAppListenerException)pe).getEventName();
399                                if(eventName==null)eventName="";
400                                
401                                call(app,pc, ON_ERROR, new Object[]{pe.getCatchBlock(pc),eventName},true);
402                                return;
403                        }
404                        catch(PageException _pe) {
405                                pe=_pe;
406                        }
407                }
408                pc.handlePageException(pe);
409        }
410
411
412        private Object call(Component app, PageContext pc, Collection.Key eventName, Object[] args, boolean catchAbort) throws PageException {
413                try {
414                        return app.call(pc, eventName, args);
415                } 
416                catch (PageException pe) {
417                        if(Abort.isSilentAbort(pe)) {
418                                if(catchAbort)
419                                        return ( pe instanceof PostContentAbort) ? Boolean.TRUE : Boolean.FALSE;
420
421                                throw pe;
422                        }
423                        throw new ModernAppListenerException(pe,eventName.getString());
424                }
425        }
426
427        private void initApplicationContext(PageContextImpl pc, ComponentPro app) throws PageException {
428                
429                // use existing app context
430                RefBoolean throwsErrorWhileInit=new RefBooleanImpl(false);
431                ModernApplicationContext appContext = new ModernApplicationContext(pc,app,throwsErrorWhileInit);
432
433                
434                pc.setApplicationContext(appContext);
435                
436                // scope cascading
437                
438                if(((UndefinedImpl)pc.undefinedScope()).getScopeCascadingType()!=appContext.getScopeCascading()) {
439                pc.undefinedScope().initialize(pc);
440        }
441        
442                
443                // ORM
444                if(appContext.isORMEnabled()) {
445                        boolean hasError=throwsErrorWhileInit.toBooleanValue();
446                        if(hasError)pc.addPageSource(app.getPageSource(), true);
447                        try{
448                                ORMUtil.resetEngine(pc,false);
449                        }
450                        finally {
451                                if(hasError)pc.removeLastPageSource(true);
452                        }
453                }
454        }
455
456
457        private static Object get(ComponentPro app, Key name,String defaultValue) {
458                Member mem = app.getMember(Component.ACCESS_PRIVATE, name, true, false);
459                if(mem==null) return defaultValue;
460                return mem.getValue();
461        }
462
463        @Override
464        public void setMode(int mode) {
465                this.mode=mode;
466        }
467
468        @Override
469        public int getMode() {
470                return mode;
471        }
472        
473
474        @Override
475        public String getType() {
476                return "modern";
477        }
478        
479        @Override
480        public boolean hasOnSessionStart(PageContext pc) {
481                return hasOnSessionStart(pc, apps.get(pc.getApplicationContext().getName()));
482        }
483        private boolean hasOnSessionStart(PageContext pc,ComponentPro app) {
484                return app!=null && app.contains(pc,ON_SESSION_START);
485        }
486}