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; 
020
021import java.net.URL;
022import java.util.Iterator;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Stack;
026import java.util.concurrent.ConcurrentHashMap;
027
028import javax.servlet.Servlet;
029import javax.servlet.ServletRequest;
030import javax.servlet.ServletResponse;
031import javax.servlet.http.HttpServlet;
032import javax.servlet.http.HttpServletRequest;
033import javax.servlet.http.HttpServletResponse;
034import javax.servlet.jsp.JspEngineInfo;
035
036import lucee.commons.io.SystemUtil;
037import lucee.commons.io.log.Log;
038import lucee.commons.io.log.LogUtil;
039import lucee.commons.io.res.util.ResourceUtil;
040import lucee.commons.lang.ExceptionUtil;
041import lucee.commons.lang.SizeOf;
042import lucee.commons.lang.SystemOut;
043import lucee.runtime.config.ConfigImpl;
044import lucee.runtime.config.ConfigWeb;
045import lucee.runtime.config.ConfigWebImpl;
046import lucee.runtime.engine.CFMLEngineImpl;
047import lucee.runtime.engine.ThreadLocalPageContext;
048import lucee.runtime.exp.Abort;
049import lucee.runtime.exp.PageException;
050import lucee.runtime.exp.PageExceptionImpl;
051import lucee.runtime.exp.RequestTimeoutException;
052import lucee.runtime.functions.string.Hash;
053import lucee.runtime.lock.LockManager;
054import lucee.runtime.op.Caster;
055import lucee.runtime.query.QueryCache;
056import lucee.runtime.type.Array;
057import lucee.runtime.type.ArrayImpl;
058import lucee.runtime.type.Struct;
059import lucee.runtime.type.StructImpl;
060import lucee.runtime.type.dt.DateTimeImpl;
061import lucee.runtime.type.scope.LocalNotSupportedScope;
062import lucee.runtime.type.scope.ScopeContext;
063import lucee.runtime.type.util.ArrayUtil;
064import lucee.runtime.type.util.KeyConstants;
065import lucee.runtime.type.util.ListUtil;
066
067/**
068 * implements a JSP Factory, this class produce JSP Compatible PageContext Object
069 * this object holds also the must interfaces to coldfusion specified functionlity
070 */
071public final class CFMLFactoryImpl extends CFMLFactory {
072        
073        private static JspEngineInfo info=new JspEngineInfoImpl("1.0");
074        private ConfigWebImpl config;
075        Stack<PageContext> pcs=new Stack<PageContext>();
076    private final Map<Integer,PageContextImpl> runningPcs=new ConcurrentHashMap<Integer, PageContextImpl>();
077    int idCounter=1;
078    private ScopeContext scopeContext=new ScopeContext(this);
079    private HttpServlet servlet;
080        private URL url=null;
081        private CFMLEngineImpl engine;
082
083        /**
084         * constructor of the JspFactory
085         * @param config Lucee specified Configuration
086         * @param compiler CFML compiler
087         * @param engine
088         */
089        public CFMLFactoryImpl(CFMLEngineImpl engine) {
090                this.engine=engine; 
091        }
092    
093    /**
094     * reset the PageContexes
095     */
096    public void resetPageContext() {
097        SystemOut.printDate(config.getOutWriter(),"Reset "+pcs.size()+" Unused PageContexts");
098        synchronized(pcs) {
099            pcs.clear();
100        }
101        
102        Iterator<PageContextImpl> it = runningPcs.values().iterator();
103        while(it.hasNext()){
104                it.next().reset();
105        }
106    }
107    
108        @Override
109        public javax.servlet.jsp.PageContext getPageContext(
110                Servlet servlet,
111                ServletRequest req,
112                ServletResponse rsp,
113                String errorPageURL,
114                boolean needsSession,
115                int bufferSize,
116                boolean autoflush) {
117                        return getPageContextImpl((HttpServlet)servlet,(HttpServletRequest)req,(HttpServletResponse)rsp,errorPageURL,needsSession,bufferSize,autoflush,true,false);
118        }
119        
120        /**
121         * similar to getPageContext Method but return the concrete implementation of the lucee PageCOntext
122         * and take the HTTP Version of the Servlet Objects
123         * @param servlet
124         * @param req
125         * @param rsp
126         * @param errorPageURL
127         * @param needsSession
128         * @param bufferSize
129         * @param autoflush
130         * @return return the page<context
131         */
132        public PageContext getLuceePageContext(
133        HttpServlet servlet,
134        HttpServletRequest req,
135        HttpServletResponse rsp,
136        String errorPageURL,
137                boolean needsSession,
138                int bufferSize,
139                boolean autoflush)  {
140        //runningCount++;
141        return getPageContextImpl(servlet, req, rsp, errorPageURL, needsSession, bufferSize, autoflush,true,false);
142        }
143        
144        public PageContextImpl getPageContextImpl(
145                        HttpServlet servlet,
146                        HttpServletRequest req,
147                        HttpServletResponse rsp,
148                        String errorPageURL,
149                                boolean needsSession,
150                                int bufferSize,
151                                boolean autoflush,boolean registerPageContext2Thread,boolean isChild)  {
152                        //runningCount++;
153                                PageContextImpl pc;
154                        synchronized (pcs) {
155                            if(pcs.isEmpty()) pc=new PageContextImpl(scopeContext,config,idCounter++,servlet);
156                            else pc=((PageContextImpl)pcs.pop());
157                            runningPcs.put(Integer.valueOf(pc.getId()),pc);
158                            this.servlet=servlet;
159                            if(registerPageContext2Thread)ThreadLocalPageContext.register(pc);
160                                
161                        }
162                        pc.initialize(servlet,req,rsp,errorPageURL,needsSession,bufferSize,autoflush,isChild);
163                        return pc;
164                        }
165
166    @Override
167        public void releasePageContext(javax.servlet.jsp.PageContext pc) {
168                releaseLuceePageContext((PageContext)pc);
169        }
170        
171        /**
172         * Similar to the releasePageContext Method, but take lucee PageContext as entry
173         * @param pc
174         */
175        public void releaseLuceePageContext(PageContext pc) {
176                if(pc.getId()<0)return;
177        pc.release();
178        ThreadLocalPageContext.release();
179        //if(!pc.hasFamily()){
180                            runningPcs.remove(Integer.valueOf(pc.getId()));
181                    
182                    if(pcs.size()<100 && ((PageContextImpl)pc).getStopPosition()==null)// not more than 100 PCs
183                        pcs.push(pc);
184                    //SystemOut.printDate(config.getOutWriter(),"Release: (id:"+pc.getId()+";running-requests:"+config.getThreadQueue().size()+";)");
185                
186       /*}
187        else {
188                 SystemOut.printDate(config.getOutWriter(),"Unlink: ("+pc.getId()+")");
189        }*/
190        }
191    
192    /**
193         * check timeout of all running threads, downgrade also priority from all thread run longer than 10 seconds
194         */
195        public void checkTimeout() {
196                if(!engine.allowRequestTimeout())return;
197                //synchronized (runningPcs) {
198            //int len=runningPcs.size();
199                        Iterator<Entry<Integer, PageContextImpl>> it = runningPcs.entrySet().iterator();
200            PageContextImpl pc;
201            //Collection.Key key;
202            Entry<Integer, PageContextImpl> e;
203            while(it.hasNext()) {
204                e = it.next();
205                pc=e.getValue();
206                
207                long timeout=pc.getRequestTimeout();
208                if(pc.getStartTime()+timeout<System.currentTimeMillis()) {
209                    terminate(pc);
210                    it.remove();
211                }
212                // after 10 seconds downgrade priority of the thread
213                else if(pc.getStartTime()+10000<System.currentTimeMillis() && pc.getThread().getPriority()!=Thread.MIN_PRIORITY) {
214                    Log log = config.getLog("requesttimeout");
215                    if(log!=null)log.log(Log.LEVEL_WARN,"controler","downgrade priority of the a thread at "+getPath(pc));
216                    try {
217                        pc.getThread().setPriority(Thread.MIN_PRIORITY);
218                    }
219                    catch(Throwable t) {
220                                ExceptionUtil.rethrowIfNecessary(t);
221                        }
222                }
223            }
224        //}
225        }
226        
227        public static void terminate(PageContextImpl pc) {
228                Log log = ((ConfigImpl)pc.getConfig()).getLog("requesttimeout");
229        
230                
231        if(log!=null)LogUtil.log(log,Log.LEVEL_ERROR,"controler",
232                        "stop thread ("+pc.getId()+") because run into a timeout "+getPath(pc)+"."+getLocks(pc),pc.getThread().getStackTrace());
233        
234        // then we release the pagecontext
235        pc.getConfig().getThreadQueue().exit(pc);
236        SystemUtil.stop(pc,createRequestTimeoutException(pc),log);
237        }
238
239        private static String getLocks(PageContext pc) {
240                String strLocks="";
241                try{
242                        LockManager manager = pc.getConfig().getLockManager();
243                String[] locks = manager.getOpenLockNames();
244                if(!ArrayUtil.isEmpty(locks)) 
245                        strLocks=" open locks at this time ("+ListUtil.arrayToList(locks, ", ")+").";
246                //LockManagerImpl.unlockAll(pc.getId());
247                }
248                catch(Throwable t){
249                        ExceptionUtil.rethrowIfNecessary(t);
250                }
251                return strLocks;
252        }
253
254        public static RequestTimeoutException createRequestTimeoutException(PageContext pc) {
255                return new RequestTimeoutException(pc.getThread(),"request ("+getPath(pc)+":"+pc.getId()+") has run into a timeout ("+(pc.getRequestTimeout()/1000)+" seconds) and has been stopped."+getLocks(pc));
256        }
257
258        private static String getPath(PageContext pc) {
259                try {
260                        String base=ResourceUtil.getResource(pc, pc.getBasePageSource()).getAbsolutePath();
261                        String current=ResourceUtil.getResource(pc, pc.getCurrentPageSource()).getAbsolutePath();
262                        if(base.equals(current)) return "path: "+base;
263                        return "path: "+base+" ("+current+")";
264                }
265                catch(Throwable t) {
266                        ExceptionUtil.rethrowIfNecessary(t);
267                        return "(fail to retrieve path:"+t.getClass().getName()+":"+t.getMessage()+")";
268                }
269        }
270        
271        @Override
272        public JspEngineInfo getEngineInfo() {
273                return info;
274        }
275
276
277        /**
278         * @return returns count of pagecontext in use
279         */
280        public int getUsedPageContextLength() { 
281                int length=0;
282                try{
283                Iterator it = runningPcs.values().iterator();
284                while(it.hasNext()){
285                        PageContextImpl pc=(PageContextImpl) it.next();
286                        if(!pc.isGatewayContext()) length++;
287                }
288                }
289                catch(Throwable t){
290                        ExceptionUtil.rethrowIfNecessary(t);
291                        return length;
292                }
293            return length;
294        }
295    /**
296     * @return Returns the config.
297     */
298    public ConfigWeb getConfig() {
299        return config;
300    }
301    public ConfigWebImpl getConfigWebImpl() {
302        return config;
303    }
304    /**
305     * @return Returns the scopeContext.
306     */
307    public ScopeContext getScopeContext() {
308        return scopeContext;
309    }
310
311    /**
312     * @return label of the factory
313     */
314    public Object getLabel() {
315        return ((ConfigWebImpl)getConfig()).getLabel();
316    }
317    /**
318     * @param label
319     */
320    public void setLabel(String label) {
321        // deprecated
322    }
323
324        /**
325         * @return the hostName
326         */
327        public URL getURL() {
328                return url;
329        }
330    
331
332        public void setURL(URL url) {
333                this.url=url;
334        }
335
336        /**
337         * @return the servlet
338         */
339        public HttpServlet getServlet() {
340                return servlet;
341        }
342
343        public void setConfig(ConfigWebImpl config) {
344                this.config=config;
345        }
346
347        public Map<Integer, PageContextImpl> getActivePageContexts() {
348                return runningPcs;
349        }
350        
351        public long getPageContextsSize() {
352                return SizeOf.size(pcs);
353        }
354        
355        public Array getInfo() {
356                Array info=new ArrayImpl();
357                
358                //synchronized (runningPcs) {
359            //int len=runningPcs.size();
360                        Iterator<PageContextImpl> it = runningPcs.values().iterator();
361            PageContextImpl pc;
362            Struct data,sctThread,scopes;
363                Thread thread;
364                Entry<Integer, PageContextImpl> e;
365                while(it.hasNext()) {
366                        pc = it.next();
367                data=new StructImpl();
368                sctThread=new StructImpl();
369                scopes=new StructImpl();
370                data.setEL("thread", sctThread);
371                data.setEL("scopes", scopes);
372                
373                if(pc.isGatewayContext()) continue;
374                thread=pc.getThread();
375                if(thread==Thread.currentThread()) continue;
376
377                
378                thread=pc.getThread();
379                if(thread==Thread.currentThread()) continue;
380                
381               
382                
383                data.setEL("startTime", new DateTimeImpl(pc.getStartTime(),false));
384                data.setEL("endTime", new DateTimeImpl(pc.getStartTime()+pc.getRequestTimeout(),false));
385                data.setEL(KeyConstants._timeout,new Double(pc.getRequestTimeout()));
386
387                
388                // thread
389                sctThread.setEL(KeyConstants._name,thread.getName());
390                sctThread.setEL("priority",Caster.toDouble(thread.getPriority()));
391                data.setEL("TagContext",PageExceptionImpl.getTagContext(pc.getConfig(),thread.getStackTrace() ));
392
393                data.setEL("urlToken", pc.getURLToken());
394                try {
395                                        if(pc.getConfig().debug())data.setEL("debugger", pc.getDebugger().getDebuggingData(pc));
396                                } catch (PageException e2) {}
397
398                try {
399                                        data.setEL("id", Hash.call(pc, pc.getId()+":"+pc.getStartTime()));
400                                } catch (PageException e1) {}
401                data.setEL("requestid", pc.getId());
402
403                // Scopes
404                scopes.setEL(KeyConstants._name, pc.getApplicationContext().getName());
405                try {
406                                        scopes.setEL(KeyConstants._application, pc.applicationScope());
407                                } catch (PageException pe) {}
408
409                try {
410                                        scopes.setEL(KeyConstants._session, pc.sessionScope());
411                                } catch (PageException pe) {}
412                
413                try {
414                                        scopes.setEL(KeyConstants._client, pc.clientScope());
415                                } catch (PageException pe) {}
416                scopes.setEL(KeyConstants._cookie, pc.cookieScope());
417                scopes.setEL(KeyConstants._variables, pc.variablesScope());
418                if(!(pc.localScope() instanceof LocalNotSupportedScope)){
419                        scopes.setEL(KeyConstants._local, pc.localScope());
420                        scopes.setEL(KeyConstants._arguments, pc.argumentsScope());
421                }
422                scopes.setEL(KeyConstants._cgi, pc.cgiScope());
423                scopes.setEL(KeyConstants._form, pc.formScope());
424                scopes.setEL(KeyConstants._url, pc.urlScope());
425                scopes.setEL(KeyConstants._request, pc.requestScope());
426                
427                info.appendEL(data);
428            }
429            return info;
430        //}
431        }
432
433        public void stopThread(String threadId, String stopType) {
434                //synchronized (runningPcs) {
435                        Iterator<PageContextImpl> it = runningPcs.values().iterator();
436            PageContext pc;
437                while(it.hasNext()) {
438                
439                pc=it.next();
440                Log log = ((ConfigImpl)pc.getConfig()).getLog("application");
441                try {
442                                        String id = Hash.call(pc, pc.getId()+":"+pc.getStartTime());
443                                        if(id.equals(threadId)){
444                                                stopType=stopType.trim();
445                                                Throwable t;
446                                                if("abort".equalsIgnoreCase(stopType) || "cfabort".equalsIgnoreCase(stopType))
447                                                        t=new Abort(Abort.SCOPE_REQUEST);
448                                                else
449                                                        t=new RequestTimeoutException(pc.getThread(),"request has been forced to stop.");
450                                                
451                                                SystemUtil.stop(pc,t,log);
452                                SystemUtil.sleep(10);
453                                                break;
454                                        }
455                                } catch (PageException e1) {}
456                
457            }
458        //}
459        }
460
461        @Override
462        public QueryCache getDefaultQueryCache() {
463        throw new RuntimeException("function PageContext.getDefaultQueryCache() no longer supported");
464        }
465}