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}