/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.runtime;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
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.JspApplicationContext;
import javax.servlet.jsp.JspEngineInfo;
import lucee.aprint;
import lucee.cli.servlet.HTTPServletImpl;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.log.Log;
import lucee.commons.io.log.LogUtil;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.SizeOf;
import lucee.commons.lang.SystemOut;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.config.ConfigImpl;
import lucee.runtime.config.ConfigWeb;
import lucee.runtime.config.ConfigWebImpl;
import lucee.runtime.config.Constants;
import lucee.runtime.engine.CFMLEngineImpl;
import lucee.runtime.engine.JspEngineInfoImpl;
import lucee.runtime.engine.Request;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.Abort;
import lucee.runtime.exp.PageException;
import lucee.runtime.exp.PageExceptionImpl;
import lucee.runtime.exp.RequestTimeoutException;
import lucee.runtime.functions.string.Hash;
import lucee.runtime.lock.LockManager;
import lucee.runtime.op.Caster;
import lucee.runtime.type.Array;
import lucee.runtime.type.ArrayImpl;
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.dt.DateTimeImpl;
import lucee.runtime.type.scope.LocalNotSupportedScope;
import lucee.runtime.type.scope.ScopeContext;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.KeyConstants;
import lucee.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 final Map<Integer,PageContextImpl> runningPcs=new ConcurrentHashMap<Integer, PageContextImpl>();
private final Map<Integer,PageContextImpl> runningChildPcs=new ConcurrentHashMap<Integer, PageContextImpl>();
int idCounter=1;
private ScopeContext scopeContext=new ScopeContext(this);
private HttpServlet _servlet;
private URL url=null;
private CFMLEngineImpl engine;
private ArrayList<String> cfmlExtensions;
private ArrayList<String> luceeExtensions;
private ServletConfig servletConfig;
public CFMLFactoryImpl(CFMLEngineImpl engine, ServletConfig sg) {
this.engine=engine;
if(engine==null)aprint.ds();
this.servletConfig=sg;
}
/**
* reset the PageContexes
*/
@Override
public void resetPageContext() {
SystemOut.printDate(config.getOutWriter(),"Reset "+pcs.size()+" Unused PageContexts");
synchronized(pcs) {
pcs.clear();
}
Iterator<PageContextImpl> it = runningPcs.values().iterator();
while(it.hasNext()){
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,-1,true,false);
}
@Override
@Deprecated
public PageContext getLuceePageContext(
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,-1,true,false);
}
@Override
public PageContext getLuceePageContext(
HttpServlet servlet,
HttpServletRequest req,
HttpServletResponse rsp,
String errorPageURL,
boolean needsSession,
int bufferSize,
boolean autoflush,boolean register, long timeout,boolean register2RunningThreads, boolean ignoreScopes) {
//runningCount++;
return getPageContextImpl(servlet, req, rsp, errorPageURL, needsSession, bufferSize, autoflush,register,false,timeout,
register2RunningThreads,ignoreScopes);
}
public PageContextImpl getPageContextImpl(
HttpServlet servlet,
HttpServletRequest req,
HttpServletResponse rsp,
String errorPageURL,
boolean needsSession,
int bufferSize,
boolean autoflush,boolean register2Thread,boolean isChild,long timeout,boolean register2RunningThreads,boolean ignoreScopes) {
PageContextImpl pc;
synchronized (pcs) {
if(pcs.isEmpty()) pc=new PageContextImpl(scopeContext,config,idCounter++,servlet,ignoreScopes);
else pc=((PageContextImpl)pcs.pop());
if(timeout>0)pc.setRequestTimeout(timeout);
if(register2RunningThreads){
runningPcs.put(Integer.valueOf(pc.getId()),pc);
if(isChild)runningChildPcs.put(Integer.valueOf(pc.getId()),pc);
}
this._servlet=servlet;
if(register2Thread)ThreadLocalPageContext.register(pc);
}
pc.initialize(servlet,req,rsp,errorPageURL,needsSession,bufferSize,autoflush,isChild,ignoreScopes);
return pc;
}
@Override
public void releasePageContext(javax.servlet.jsp.PageContext pc) {
releaseLuceePageContext((PageContext)pc);
}
@Override
public CFMLEngine getEngine() {
return engine;
}
@Override
@Deprecated
public void releaseLuceePageContext(PageContext pc) {
releaseLuceePageContext(pc, true);
}
/**
* Similar to the releasePageContext Method, but take lucee PageContext as entry
* @param pc
*/
@Override
public void releaseLuceePageContext(PageContext pc, boolean unregister) {
if(pc.getId()<0)return;
pc.release();
if(unregister)ThreadLocalPageContext.release();
runningPcs.remove(Integer.valueOf(pc.getId()));
if(pc.getParentPageContext()!=null)runningChildPcs.remove(Integer.valueOf(pc.getId()));
if(pcs.size()<100 && ((PageContextImpl)pc).getTimeoutStackTrace()==null)// not more than 100 PCs
pcs.push(pc);
}
/**
* check timeout of all running threads, downgrade also priority from all thread run longer than 10 seconds
*/
@Override
public void checkTimeout() {
if(!engine.allowRequestTimeout())return;
//synchronized (runningPcs) {
//int len=runningPcs.size();
// we only terminate child threads
Iterator<Entry<Integer, PageContextImpl>> it = runningChildPcs.entrySet().iterator();
PageContextImpl pc;
Entry<Integer, PageContextImpl> e;
while(it.hasNext()) {
e = it.next();
pc=e.getValue();
long timeout=pc.getRequestTimeout();
if(pc.getStartTime()+timeout<System.currentTimeMillis()) {
terminate(pc,true);
runningPcs.remove(Integer.valueOf(pc.getId()));
it.remove();
}
// after 10 seconds downgrade priority of the thread
else if(pc.getStartTime()+10000<System.currentTimeMillis() && pc.getThread().getPriority()!=Thread.MIN_PRIORITY) {
Log log = config.getLog("requesttimeout");
if(log!=null)log.log(Log.LEVEL_WARN,"controler","downgrade priority of the a thread at "+getPath(pc));
try {
pc.getThread().setPriority(Thread.MIN_PRIORITY);
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
}
}
//}
}
public static void terminate(PageContextImpl pc, boolean async) {
Log log = ((ConfigImpl)pc.getConfig()).getLog("requesttimeout");
if(log!=null)LogUtil.log(log,Log.LEVEL_ERROR,"controler",
"stop thread ("+pc.getId()+") because run into a timeout "+getPath(pc)+"."+RequestTimeoutException.locks(pc),pc.getThread().getStackTrace());
pc.getConfig().getThreadQueue().exit(pc);
SystemUtil.stop(pc,log,async);
}
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(NullPointerException npe) {
return "(no path available)";
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return "(fail to retrieve path:"+t.getClass().getName()+":"+t.getMessage()+")";
}
}
@Override
public JspEngineInfo getEngineInfo() {
return info;
}
/**
* @return returns count of pagecontext in use
*/
@Override
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){
ExceptionUtil.rethrowIfNecessary(t);
return length;
}
return length;
}
/**
* @return Returns the config.
*/
@Override
public ConfigWeb getConfig() {
return config;
}
public ConfigWebImpl getConfigWebImpl() {
return config;
}
/**
* @return Returns the scopeContext.
*/
public ScopeContext getScopeContext() {
return scopeContext;
}
/**
* @return label of the factory
*/
@Override
public Object getLabel() {
return ((ConfigWebImpl)getConfig()).getLabel();
}
/**
* @param label
*/
@Override
public void setLabel(String label) {
// deprecated
}
@Override
public URL getURL() {
return url;
}
public void setURL(URL url) {
this.url=url;
}
/**
* @return the servlet
*/
@Override
public HttpServlet getServlet() {
if(_servlet==null)_servlet=new HTTPServletImpl(servletConfig, servletConfig.getServletContext(), servletConfig.getServletName());;
return _servlet;
}
public void setConfig(ConfigWebImpl config) {
this.config=config;
}
public Map<Integer, PageContextImpl> getActivePageContexts() {
return runningPcs;
}
public long getPageContextsSize() {
return SizeOf.size(pcs);
}
public Array getInfo() {
Array info=new ArrayImpl();
//synchronized (runningPcs) {
//int len=runningPcs.size();
Iterator<PageContextImpl> it = runningPcs.values().iterator();
PageContextImpl pc;
Struct data,sctThread,scopes;
Thread thread;
Entry<Integer, PageContextImpl> e;
while(it.hasNext()) {
pc = it.next();
data=new StructImpl();
sctThread=new StructImpl();
scopes=new StructImpl();
data.setEL("thread", sctThread);
data.setEL("scopes", scopes);
if(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 pe) {}
try {
scopes.setEL(KeyConstants._session, pc.sessionScope());
} catch (PageException pe) {}
try {
scopes.setEL(KeyConstants._client, pc.clientScope());
} catch (PageException pe) {}
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) {
Iterator<PageContextImpl> it = runningPcs.values().iterator();
PageContext pc;
while(it.hasNext()) {
pc=it.next();
Log log = ((ConfigImpl)pc.getConfig()).getLog("application");
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))
throw new RuntimeException("type ["+stopType+"] is no longer supported");
//t=new Abort(Abort.SCOPE_REQUEST);
//else t=new RequestTimeoutException(pc.getThread(),"request has been forced to stop.");
SystemUtil.stop(pc,log,true);
SystemUtil.sleep(10);
break;
}
} catch (PageException e1) {}
}
//}
}
@Override
public JspApplicationContext getJspApplicationContext(ServletContext arg0) {
throw new RuntimeException("not supported!");
}
@Override
public int toDialect(String ext) {
// MUST improve perfomance
if(cfmlExtensions==null) _initExtensions();
if(cfmlExtensions.contains(ext.toLowerCase())) return CFMLEngine.DIALECT_CFML;
return CFMLEngine.DIALECT_CFML;
}
// FUTURE add to loader
public int toDialect(String ext, int defaultValue) {
if(ext==null) return defaultValue;
if(cfmlExtensions==null) _initExtensions();
if(cfmlExtensions.contains(ext=ext.toLowerCase())) return CFMLEngine.DIALECT_CFML;
if(luceeExtensions.contains(ext)) return CFMLEngine.DIALECT_LUCEE;
return defaultValue;
}
private void _initExtensions() {
cfmlExtensions=new ArrayList<String>();
luceeExtensions=new ArrayList<String>();
try {
Iterator<?> it = getServlet().getServletContext().getServletRegistrations().entrySet().iterator();
Entry<String,? extends ServletRegistration> e;
String cn;
while(it.hasNext()){
e = (Entry<String, ? extends ServletRegistration>) it.next();
cn=e.getValue().getClassName();
if(cn!=null && cn.indexOf("LuceeServlet")!=-1) {
setExtensions(luceeExtensions,e.getValue().getMappings().iterator());
}
else if(cn!=null && cn.indexOf("CFMLServlet")!=-1) {
setExtensions(cfmlExtensions,e.getValue().getMappings().iterator());
}
}
}
catch(Throwable t){
ExceptionUtil.rethrowIfNecessary(t);
ArrayUtil.addAll(cfmlExtensions,Constants.getCFMLExtensions());
ArrayUtil.addAll(luceeExtensions,Constants.getLuceeExtensions());
}
}
private void setExtensions(ArrayList<String> extensions, Iterator<String> it) {
String str,str2;
Iterator<String> it2;
while(it.hasNext()){
str=it.next();
it2=ListUtil.listToSet(str, ',', true).iterator();
while(it2.hasNext()){
str2=it2.next();
extensions.add(str2.substring(2));// MUSTMUST better impl
}
}
}
@Override
public Iterator<String> getCFMLExtensions() {
if(cfmlExtensions==null) _initExtensions();
return cfmlExtensions.iterator();
}
@Override
public Iterator<String> getLuceeExtensions() {
if(luceeExtensions==null) _initExtensions();
return luceeExtensions.iterator();
}
public static RequestTimeoutException createRequestTimeoutException(PageContext pc) {
return new RequestTimeoutException(pc, pc.getThread().getStackTrace());
}
}