Index: test/org/apache/jasper/EmbeddedServletOptionsTest.java =================================================================== --- test/org/apache/jasper/EmbeddedServletOptionsTest.java (revision 0) +++ test/org/apache/jasper/EmbeddedServletOptionsTest.java (revision 0) @@ -0,0 +1,56 @@ +package org.apache.jasper; + +import javax.servlet.ServletContext; + +import org.apache.catalina.Container; +import org.apache.catalina.Engine; +import org.apache.catalina.core.ApplicationContext; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.core.StandardEngine; +import org.apache.catalina.core.StandardService; +import org.apache.catalina.core.StandardWrapper; + +import junit.framework.TestCase; + +/** + * Currently only tests whether maxLoadedJsps initializes correctly. + * */ +public class EmbeddedServletOptionsTest extends TestCase { + StandardWrapper config; + ServletContext context; + + protected void setUp() throws Exception { + super.setUp(); + config = new StandardWrapper(); + StandardContext std1 = new StandardContext(); + StandardContext std2 = new StandardContext(); + Engine std3 = new StandardEngine(); + std3.setService(new StandardService()); + std2.setParent(std3); + std1.setParent(std2); + context = new ApplicationContext(std1); + } + + public void testGetMaxLoadedJspsNegative() { + config.addInitParameter("maxLoadedJsps", "-1"); + EmbeddedServletOptions options = new EmbeddedServletOptions(config, context); + assertEquals(-1, options.getMaxLoadedJsps()); + } + + public void testGetMaxLoadedJspsDefault() { + EmbeddedServletOptions options = new EmbeddedServletOptions(config, context); + assertEquals(-1, options.getMaxLoadedJsps()); + } + + public void testGetMaxLoadedJspsPositive() { + config.addInitParameter("maxLoadedJsps", "2000"); + EmbeddedServletOptions options = new EmbeddedServletOptions(config, context); + assertEquals(2000, options.getMaxLoadedJsps()); + } + + public void testGetMaxLoadedJspsException() { + config.addInitParameter("maxLoadedJsps", "2000abc"); + EmbeddedServletOptions options = new EmbeddedServletOptions(config, context); + assertEquals(-1, options.getMaxLoadedJsps()); + } +} Index: test/org/apache/jasper/compiler/JspRuntimeContextTest.java =================================================================== --- test/org/apache/jasper/compiler/JspRuntimeContextTest.java (revision 0) +++ test/org/apache/jasper/compiler/JspRuntimeContextTest.java (revision 0) @@ -0,0 +1,82 @@ +package org.apache.jasper.compiler; + +import static org.easymock.EasyMock.*; + +import org.apache.catalina.Engine; +import org.apache.catalina.core.ApplicationContext; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.core.StandardEngine; +import org.apache.catalina.core.StandardService; +import org.apache.catalina.core.StandardWrapper; +import org.apache.jasper.EmbeddedServletOptions; +import org.apache.jasper.JasperException; +import org.apache.jasper.Options; +import org.apache.jasper.servlet.JspServletWrapper; + +import junit.framework.TestCase; + +public class JspRuntimeContextTest extends TestCase { + + JspRuntimeContext context; + ApplicationContext ctxt; + Options options; + + protected void setUp() throws Exception { + super.setUp(); + StandardWrapper config = new StandardWrapper(); + config.addInitParameter("maxLoadedJsps", "2"); + StandardContext std1 = new StandardContext(); + StandardContext std2 = new StandardContext(); + Engine std3 = new StandardEngine(); + std3.setService(new StandardService()); + std2.setParent(std3); + std1.setParent(std2); + ctxt = new ApplicationContext(std1); + options = new EmbeddedServletOptions(config, ctxt); + + context = new JspRuntimeContext(ctxt, options); + } + + public void testCheckUnloadDisabled() { + options = (Options) createMock(Options.class); + expect(options.getScratchDir()).andReturn(null); + expect(options.getClassPath()).andReturn(null); + expect(options.getDevelopment()).andReturn(false); + expect(options.getMaxLoadedJsps()).andReturn(-1); + replay(options); + context = new JspRuntimeContext(ctxt, options); + context.checkUnload(); + verify(options); + } + + public void testCheckUnloadEnabledSizeTooFewJsps() { + options = (Options) createMock(Options.class); + expect(options.getScratchDir()).andReturn(null); + expect(options.getClassPath()).andReturn(null); + expect(options.getDevelopment()).andReturn(false); + expect(options.getMaxLoadedJsps()).andReturn(1); + expect(options.getCheckInterval()).andReturn(0); + expect(options.getMaxLoadedJsps()).andReturn(1); + replay(options); + context = new JspRuntimeContext(ctxt, options); + context.checkUnload(); + verify(options); + } + + public void testCheckUnloadEnabledSizeEnoughJsps() throws JasperException { + options = (Options) createMock(Options.class); + expect(options.getScratchDir()).andReturn(null); + expect(options.getClassPath()).andReturn(null); + expect(options.getDevelopment()).andReturn(false); + expect(options.getMaxLoadedJsps()).andReturn(1); + expect(options.getCheckInterval()).andReturn(0); + expect(options.getMaxLoadedJsps()).andReturn(1); + expect(options.getMaxLoadedJsps()).andReturn(1); + replay(options); + context = new JspRuntimeContext(ctxt, options); + context.addWrapper("first", new JspServletWrapper(new StandardWrapper(), options, "/first.jsp", false, context)); + context.addWrapper("second", new JspServletWrapper(new StandardWrapper(), options, "/second.jsp", false, context)); + context.checkUnload(); + verify(options); + } +} Index: java/org/apache/jasper/JspC.java =================================================================== --- java/org/apache/jasper/JspC.java (revision 888769) +++ java/org/apache/jasper/JspC.java (working copy) @@ -447,6 +447,10 @@ return cache; } + public int getMaxLoadedJsps() { + return -1; + } + /** * Background compilation check intervals in seconds */ Index: java/org/apache/jasper/servlet/JspServlet.java =================================================================== --- java/org/apache/jasper/servlet/JspServlet.java (revision 888769) +++ java/org/apache/jasper/servlet/JspServlet.java (working copy) @@ -286,6 +286,7 @@ public void periodicEvent() { + rctxt.checkUnload(); rctxt.checkCompile(); } Index: java/org/apache/jasper/servlet/JspServletWrapper.java =================================================================== --- java/org/apache/jasper/servlet/JspServletWrapper.java (revision 888769) +++ java/org/apache/jasper/servlet/JspServletWrapper.java (working copy) @@ -81,6 +81,7 @@ private JasperException compileException; private long servletClassLastModifiedTime; private long lastModificationTest = 0L; + private long lastExecutionTime = 0L; /* * JspServletWrapper for JSP pages. @@ -310,6 +311,10 @@ // The following sets reload to true, if necessary ctxt.compile(); + + if (options.getDevelopment() && options.getMaxLoadedJsps() > 0) { + ctxt.getRuntimeContext().unloadJsp(); + } } } else { if (compileException != null) { @@ -371,7 +376,7 @@ } else { theServlet.service(request, response); } - + this.lastExecutionTime = System.currentTimeMillis(); } catch (UnavailableException ex) { String includeRequestUri = (String) request.getAttribute("javax.servlet.include.request_uri"); @@ -418,6 +423,10 @@ } } + public long getLastExecutionTime() { + return lastExecutionTime; + } + public void destroy() { if (theServlet != null) { theServlet.destroy(); Index: java/org/apache/jasper/EmbeddedServletOptions.java =================================================================== --- java/org/apache/jasper/EmbeddedServletOptions.java (revision 888769) +++ java/org/apache/jasper/EmbeddedServletOptions.java (working copy) @@ -181,6 +181,12 @@ private boolean displaySourceFragment = true; + /** + * The maxim number of loaded jsps per web-application. If there are more + * jsps loaded, they will be unloaded. + */ + private int maxLoadedJsps = -1; + public String getProperty(String name ) { return settings.getProperty( name ); } @@ -371,6 +377,14 @@ } /** + * Should any jsps be unloaded? If set to a value greater than 0 eviction of jsps + * is started. Default: -1 + * */ + public int getMaxLoadedJsps() { + return maxLoadedJsps; + } + + /** * Create an EmbeddedServletOptions object using data available from * ServletConfig and ServletContext. */ @@ -639,6 +653,17 @@ } } + String maxLoadedJsps = config.getInitParameter("maxLoadedJsps"); + if (maxLoadedJsps != null) { + try { + this.maxLoadedJsps = Integer.parseInt(maxLoadedJsps); + } catch(NumberFormatException ex) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.maxLoadedJsps", ""+this.maxLoadedJsps)); + } + } + } + // Setup the global Tag Libraries location cache for this // web-application. tldLocationsCache = new TldLocationsCache(context); Index: java/org/apache/jasper/compiler/JspRuntimeContext.java =================================================================== --- java/org/apache/jasper/compiler/JspRuntimeContext.java (revision 888769) +++ java/org/apache/jasper/compiler/JspRuntimeContext.java (working copy) @@ -28,6 +28,7 @@ import java.security.cert.Certificate; import java.util.Iterator; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -43,6 +44,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; + /** * Class for tracking JSP compile time file dependencies when the * &060;%@include file="..."%&062; directive is used. @@ -171,7 +173,6 @@ * Maps JSP pages to their JspServletWrapper's */ private Map jsps = new ConcurrentHashMap(); - // ------------------------------------------------------ Public Methods @@ -468,5 +469,53 @@ return new SecurityHolder(source, permissions); } + /** Returns a JspServletWrapper that should be destroyed. Default strategy: Least recently used. */ + public JspServletWrapper getJspForUnload(final int maxLoadedJsps) { + if( jsps.size() > maxLoadedJsps ) { + synchronized( jsps ) { + Entry oldest = null; + for (Entry jsw : jsps.entrySet()) { + if (!jsw.getValue().isTagFile()) { + if (oldest == null || oldest.getValue().getLastExecutionTime() > jsw.getValue().getLastExecutionTime()) { + oldest = jsw; + } + } + } + if (oldest != null) { + removeWrapper(oldest.getKey()); + return oldest.getValue(); + } + } + } + return null; + } + + /** + * Method used by background thread to check if any JSP's should be destroyed. + * If JSP's to be unloaded are found, they will be destroyed. + * Uses the lastCheck time from background compiler to determine if it is time to unload JSP's. + */ + public void checkUnload() { + if (options.getMaxLoadedJsps() > 0) { + long now = System.currentTimeMillis(); + if (now > (lastCheck + (options.getCheckInterval() * 1000L))) { + while (unloadJsp()); + } + } + } + + /** + * Checks whether there is a jsp to unload, if one is found, it is destroyed. + * */ + public boolean unloadJsp() { + JspServletWrapper jsw = getJspForUnload(options.getMaxLoadedJsps()); + if( null != jsw ) { + synchronized(jsw) { + jsw.destroy(); + return true; + } + } + return false; + } } Index: java/org/apache/jasper/Options.java =================================================================== --- java/org/apache/jasper/Options.java (revision 888769) +++ java/org/apache/jasper/Options.java (working copy) @@ -194,4 +194,10 @@ */ public Map getCache(); + /** + * The maxim number of loaded jsps per web-application. If there are more + * jsps loaded, they will be unloaded. If unset or less than 0, no jsps + * are unloaded. + */ + public int getMaxLoadedJsps(); } Index: build.xml =================================================================== --- build.xml (revision 888769) +++ build.xml (working copy) @@ -859,6 +859,12 @@ + + + + + + @@ -884,7 +890,7 @@ - + Index: .classpath =================================================================== --- .classpath (revision 888769) +++ .classpath (working copy) @@ -7,6 +7,7 @@ + Index: build.properties.default =================================================================== --- build.properties.default (revision 888769) +++ build.properties.default (working copy) @@ -50,6 +50,12 @@ # Mirror, was used when there were problems with the main SF downloads site # base-sf.loc=http://sunet.dl.sourceforge.net +# ----- Easymock ---- +easymock.version=2.4 +easymock.loc=http://mirrors.ibiblio.org/pub/mirrors/maven2/org/easymock/easymock/${easymock.version}/easymock_{easymock.version}.jar +easymock.dir=${base.path}/easymock/ +easymock.jar=${easymock.dir}/easymock.${easymock.version}.jar + # ----- Commons Logging, version 1.1 or later ----- commons-logging-version=1.1.1 commons-logging-src.loc=${base-commons.loc}/logging/source/commons-logging-${commons-logging-version}-src.tar.gz