package railo.runtime; import java.net.URL; import java.util.Iterator; import java.util.Stack; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.JspEngineInfo; import railo.commons.io.SystemUtil; import railo.commons.io.log.Log; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.SizeOf; import railo.commons.lang.SystemOut; import railo.runtime.config.ConfigWeb; import railo.runtime.config.ConfigWebImpl; import railo.runtime.engine.CFMLEngineImpl; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.exp.Abort; import railo.runtime.exp.PageException; import railo.runtime.exp.PageExceptionImpl; import railo.runtime.exp.RequestTimeoutException; import railo.runtime.functions.string.Hash; import railo.runtime.lock.LockManager; import railo.runtime.op.Caster; import railo.runtime.query.QueryCache; import railo.runtime.type.Array; import railo.runtime.type.ArrayImpl; import railo.runtime.type.Collection; import railo.runtime.type.Collection.Key; import railo.runtime.type.KeyImpl; import railo.runtime.type.Struct; import railo.runtime.type.StructImpl; import railo.runtime.type.dt.DateTimeImpl; import railo.runtime.type.scope.ArgumentIntKey; import railo.runtime.type.scope.LocalNotSupportedScope; import railo.runtime.type.scope.ScopeContext; import railo.runtime.type.util.ArrayUtil; import railo.runtime.type.util.KeyConstants; import railo.runtime.type.util.ListUtil; /** * implements a JSP Factory, this class produce JSP Compatible PageContext Object * this object holds also the must interfaces to coldfusion specified functionlity */ public final class CFMLFactoryImpl extends CFMLFactory { private static JspEngineInfo info=new JspEngineInfoImpl("1.0"); private ConfigWebImpl config; Stack<PageContext> pcs=new Stack<PageContext>(); private Struct runningPcs=new StructImpl(); int idCounter=1; private QueryCache queryCache; private ScopeContext scopeContext=new ScopeContext(this); private HttpServlet servlet; private URL url=null; private CFMLEngineImpl engine; /** * constructor of the JspFactory * @param config Railo specified Configuration * @param compiler CFML compiler * @param engine */ public CFMLFactoryImpl(CFMLEngineImpl engine,QueryCache queryCache) { this.engine=engine; this.queryCache=queryCache; } /** * reset the PageContexes */ public void resetPageContext() { SystemOut.printDate(config.getOutWriter(),"Reset "+pcs.size()+" Unused PageContexts"); synchronized(pcs) { pcs.clear(); } if(runningPcs!=null) { synchronized(runningPcs) { Iterator<Object> it = runningPcs.valueIterator(); while(it.hasNext()){ ((PageContextImpl)it.next()).reset(); } } } } @Override public javax.servlet.jsp.PageContext getPageContext( Servlet servlet, ServletRequest req, ServletResponse rsp, String errorPageURL, boolean needsSession, int bufferSize, boolean autoflush) { return getPageContextImpl((HttpServlet)servlet,(HttpServletRequest)req,(HttpServletResponse)rsp,errorPageURL,needsSession,bufferSize,autoflush,true,false); } /** * similar to getPageContext Method but return the concrete implementation of the railo PageCOntext * and take the HTTP Version of the Servlet Objects * @param servlet * @param req * @param rsp * @param errorPageURL * @param needsSession * @param bufferSize * @param autoflush * @return return the page<context */ public PageContext getRailoPageContext( HttpServlet servlet, HttpServletRequest req, HttpServletResponse rsp, String errorPageURL, boolean needsSession, int bufferSize, boolean autoflush) { //runningCount++; return getPageContextImpl(servlet, req, rsp, errorPageURL, needsSession, bufferSize, autoflush,true,false); } public PageContextImpl getPageContextImpl( HttpServlet servlet, HttpServletRequest req, HttpServletResponse rsp, String errorPageURL, boolean needsSession, int bufferSize, boolean autoflush,boolean registerPageContext2Thread,boolean isChild) { //runningCount++; PageContextImpl pc; synchronized (pcs) { if(pcs.isEmpty()) pc=new PageContextImpl(scopeContext,config,queryCache,idCounter++,servlet); else pc=((PageContextImpl)pcs.pop()); runningPcs.setEL(ArgumentIntKey.init(pc.getId()),pc); this.servlet=servlet; if(registerPageContext2Thread)ThreadLocalPageContext.register(pc); } pc.initialize(servlet,req,rsp,errorPageURL,needsSession,bufferSize,autoflush,isChild); return pc; } @Override public void releasePageContext(javax.servlet.jsp.PageContext pc) { releaseRailoPageContext((PageContext)pc); } /** * Similar to the releasePageContext Method, but take railo PageContext as entry * @param pc */ public void releaseRailoPageContext(PageContext pc) { if(pc.getId()<0)return; pc.release(); ThreadLocalPageContext.release(); //if(!pc.hasFamily()){ synchronized (runningPcs) { runningPcs.removeEL(ArgumentIntKey.init(pc.getId())); if(pcs.size()<100)// not more than 100 PCs pcs.push(pc); //SystemOut.printDate(config.getOutWriter(),"Release: (id:"+pc.getId()+";running-requests:"+config.getThreadQueue().size()+";)"); } /*} else { SystemOut.printDate(config.getOutWriter(),"Unlink: ("+pc.getId()+")"); }*/ } /** * check timeout of all running threads, downgrade also priority from all thread run longer than 10 seconds */ public void checkTimeout() { if(!engine.allowRequestTimeout())return; synchronized (runningPcs) { //int len=runningPcs.size(); Iterator it = runningPcs.keyIterator(); PageContext pc; Collection.Key key; while(it.hasNext()) { key=KeyImpl.toKey(it.next(),null); //print.out("key:"+key); pc=(PageContext) runningPcs.get(key,null); if(pc==null) { runningPcs.removeEL(key); continue; } long timeout=pc.getRequestTimeout(); if(pc.getStartTime()+timeout<System.currentTimeMillis()) { terminate(pc); } // after 10 seconds downgrade priority of the thread else if(pc.getStartTime()+10000<System.currentTimeMillis() && pc.getThread().getPriority()!=Thread.MIN_PRIORITY) { Log log = config.getRequestTimeoutLogger(); if(log!=null)log.warn("controller","downgrade priority of the a thread at "+getPath(pc)); try { pc.getThread().setPriority(Thread.MIN_PRIORITY); } catch(Throwable t) {} } } } } public static void terminate(PageContext pc) { Log log = pc.getConfig().getRequestTimeoutLogger(); String strLocks=""; try{ LockManager manager = pc.getConfig().getLockManager(); String[] locks = manager.getOpenLockNames(); if(!ArrayUtil.isEmpty(locks)) strLocks=" open locks at this time ("+ListUtil.arrayToList(locks, ", ")+")."; //LockManagerImpl.unlockAll(pc.getId()); } catch(Throwable t){} if(log!=null)log.error("controller", "stop thread ("+pc.getId()+") because run into a timeout "+getPath(pc)+"."+strLocks); pc.getThread().stop(new RequestTimeoutException(pc,"request ("+getPath(pc)+":"+pc.getId()+") has run into a timeout ("+(pc.getRequestTimeout()/1000)+" seconds) and has been stopped."+strLocks)); } private static String getPath(PageContext pc) { try { String base=ResourceUtil.getResource(pc, pc.getBasePageSource()).getAbsolutePath(); String current=ResourceUtil.getResource(pc, pc.getCurrentPageSource()).getAbsolutePath(); if(base.equals(current)) return "path: "+base; return "path: "+base+" ("+current+")"; } catch(Throwable t) { return ""; } } @Override public JspEngineInfo getEngineInfo() { return info; } /** * @return returns count of pagecontext in use */ public int getUsedPageContextLength() { int length=0; try{ Iterator it = runningPcs.values().iterator(); while(it.hasNext()){ PageContextImpl pc=(PageContextImpl) it.next(); if(!pc.isGatewayContext()) length++; } } catch(Throwable t){ return length; } return length; } /** * @return Returns the config. */ public ConfigWeb getConfig() { return config; } public ConfigWebImpl getConfigWebImpl() { return config; } /** * @return Returns the scopeContext. */ public ScopeContext getScopeContext() { return scopeContext; } /** * @return label of the factory */ public Object getLabel() { return ((ConfigWebImpl)getConfig()).getLabel(); } /** * @param label */ public void setLabel(String label) { // deprecated } /** * @return the hostName */ public URL getURL() { return url; } public void setURL(URL url) { this.url=url; } /** * @return the servlet */ public HttpServlet getServlet() { return servlet; } public void setConfig(ConfigWebImpl config) { this.config=config; } public Struct getRunningPageContexts() { return runningPcs; } // exists because it is used in Morpheus public Struct getRunningPageContextes() { return getRunningPageContexts(); } public long getPageContextsSize() { return SizeOf.size(pcs); } public Array getInfo() { Array info=new ArrayImpl(); synchronized (runningPcs) { //int len=runningPcs.size(); Iterator<Key> it = runningPcs.keyIterator(); PageContextImpl pc; Struct data,sctThread,scopes; Collection.Key key; Thread thread; while(it.hasNext()) { data=new StructImpl(); sctThread=new StructImpl(); scopes=new StructImpl(); data.setEL("thread", sctThread); data.setEL("scopes", scopes); key=KeyImpl.toKey(it.next(),null); //print.out("key:"+key); pc=(PageContextImpl) runningPcs.get(key,null); if(pc==null || pc.isGatewayContext()) continue; thread=pc.getThread(); if(thread==Thread.currentThread()) continue; thread=pc.getThread(); if(thread==Thread.currentThread()) continue; data.setEL("startTime", new DateTimeImpl(pc.getStartTime(),false)); data.setEL("endTime", new DateTimeImpl(pc.getStartTime()+pc.getRequestTimeout(),false)); data.setEL(KeyConstants._timeout,new Double(pc.getRequestTimeout())); // thread sctThread.setEL(KeyConstants._name,thread.getName()); sctThread.setEL("priority",Caster.toDouble(thread.getPriority())); data.setEL("TagContext",PageExceptionImpl.getTagContext(pc.getConfig(),thread.getStackTrace() )); data.setEL("urlToken", pc.getURLToken()); try { if(pc.getConfig().debug())data.setEL("debugger", pc.getDebugger().getDebuggingData(pc)); } catch (PageException e2) {} try { data.setEL("id", Hash.call(pc, pc.getId()+":"+pc.getStartTime())); } catch (PageException e1) {} data.setEL("requestid", pc.getId()); // Scopes scopes.setEL(KeyConstants._name, pc.getApplicationContext().getName()); try { scopes.setEL(KeyConstants._application, pc.applicationScope()); } catch (PageException e) {} try { scopes.setEL(KeyConstants._session, pc.sessionScope()); } catch (PageException e) {} try { scopes.setEL(KeyConstants._client, pc.clientScope()); } catch (PageException e) {} scopes.setEL(KeyConstants._cookie, pc.cookieScope()); scopes.setEL(KeyConstants._variables, pc.variablesScope()); if(!(pc.localScope() instanceof LocalNotSupportedScope)){ scopes.setEL(KeyConstants._local, pc.localScope()); scopes.setEL(KeyConstants._arguments, pc.argumentsScope()); } scopes.setEL(KeyConstants._cgi, pc.cgiScope()); scopes.setEL(KeyConstants._form, pc.formScope()); scopes.setEL(KeyConstants._url, pc.urlScope()); scopes.setEL(KeyConstants._request, pc.requestScope()); info.appendEL(data); } return info; } } public void stopThread(String threadId, String stopType) { synchronized (runningPcs) { //int len=runningPcs.size(); Iterator it = runningPcs.keyIterator(); PageContext pc; while(it.hasNext()) { pc=(PageContext) runningPcs.get(KeyImpl.toKey(it.next(),null),null); if(pc==null) continue; try { String id = Hash.call(pc, pc.getId()+":"+pc.getStartTime()); if(id.equals(threadId)){ stopType=stopType.trim(); Throwable t; if("abort".equalsIgnoreCase(stopType) || "cfabort".equalsIgnoreCase(stopType)) t=new Abort(Abort.SCOPE_REQUEST); else t=new RequestTimeoutException(pc,"request has been forced to stop."); pc.getThread().stop(t); SystemUtil.sleep(10); break; } } catch (PageException e1) {} } } } @Override public QueryCache getDefaultQueryCache() { return queryCache; } }