summary: fixed classloader leak by making the thread stop on webapp unload

this fix requires clients (those using proxy authentication) to configure either
a) a listener in web.xml (see CleanUpListener)
b) quartz in a spring xml config file (see CleanUpJob)

each of these starts a thread which runs the clean up operation

the listener will shut itself down on app undeploy
Spring's SchedulerFactoryBean (a quartz helper) must be configured with a destroy-method="destroy" in the spring xml config, otherwise a classloader leak may occur on webapp undeploy/hot deploy
This commit is contained in:
Brad Cupit 2009-02-13 22:30:38 +00:00
parent 7d4c321b3c
commit 5761e0cb61
14 changed files with 663 additions and 56 deletions

View File

@ -101,6 +101,22 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>opensymphony</groupId>
<artifactId>quartz-all</artifactId>
<version>1.6.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>

View File

@ -0,0 +1,59 @@
package org.jasig.cas.client.cleanup;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
* One of two timer implementations that regularly calls
* {@link CleanUpRegistry#cleanAll()}. This implementation
* is configured in via Spring and does not require any changes
* to web.xml
* <p>
* {@link CleanUpListener}, the other implementation, does require
* changes to web.xml, but does not require any Spring configuration.
* <p>
* The decision as to which timer implementation you use is entirely
* subjective, as both perform the same operation.
* <p>
* Example Spring config:
* <code>
* <bean id="cleanUpJob" class="org.springframework.scheduling.quartz.JobDetailBean">
* <property name="jobClass" value="org.jasig.cas.client.cleanup.CleanUpJob"/>
* </bean>
*
* <bean id="cleanUpTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
* <property name="jobDetail" ref="cleanUpJob"/>
* <property name="startDelay" value="60000"/>
* <property name="repeatInterval" value="60000"/>
* </bean>
*
* <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" destroy-method="destroy">
* <property name="triggers">
* <list>
* <ref bean="cleanUpTrigger"/>
* </list>
* </property>
* </bean>
* </code>
*
* Note the destroy-method attribute of SchedulerFactoryBean. Without this attribute,
* a classloader leak may occur.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class CleanUpJob extends QuartzJobBean {
private final CleanUpRegistry cleanUpRegistry;
public CleanUpJob() {
this.cleanUpRegistry = CleanUpRegistryImpl.getInstance();
}
public CleanUpJob(CleanUpRegistry cleanUpRegistry) {
this.cleanUpRegistry = cleanUpRegistry;
}
protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
cleanUpRegistry.cleanAll();
}
}

View File

@ -0,0 +1,97 @@
package org.jasig.cas.client.cleanup;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* One of two timer implementations that regularly calls
* {@link CleanUpRegistry#cleanAll()}. This implementation
* is configured in web.xml and does not require any Spring
* configuration.
* <p>
* {@link CleanUpJob}, the other implementation, does require
* Spring, but does not require changes to web.xml
* <p>
* The decision as to which timer implementation you use is entirely
* subjective, as both perform the same operation.
* <p>
* You can customize the time between cleanup runs by setting a
* context-param in web.xml
* <p>
* Example web.xml config:
* <code>
* <context-param>
* <param-name>millisBetweenCleanUps</param-name>
* <param-value>45000</param-value>
* </context-param>
* </code>
*
* With this listener configured, a timer will be set to clean up any {@link Cleanable}s.
* This timer will automatically shut down on webapp undeploy, preventing any classloader
* leaks from occurring.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class CleanUpListener implements ServletContextListener {
protected static final int DEFAULT_MILLIS_BETWEEN_CLEANUPS = 60 * 1000;
protected static final String MILLIS_BETWEEN_CLEANUPS_INIT_PARAM = "millisBetweenCleanUps";
private final Timer timer;
private final CleanUpRegistry cleanUpRegistry;
public CleanUpListener() {
this.timer = new Timer(true);
this.cleanUpRegistry = CleanUpRegistryImpl.getInstance();
}
protected CleanUpListener(Timer timer, CleanUpRegistry cleanUpRegistry) {
this.timer = timer;
this.cleanUpRegistry = cleanUpRegistry;
}
public void contextInitialized(ServletContextEvent servletContextEvent) {
final Cleaner cleaner = new Cleaner(this.cleanUpRegistry);
final long millisBetweenCleanUps = getMillisBetweenCleanups(servletContextEvent.getServletContext());
final long millisBeforeStart = millisBetweenCleanUps;
this.timer.schedule(cleaner, millisBeforeStart, millisBetweenCleanUps);
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
this.timer.cancel();
}
protected long getMillisBetweenCleanups(ServletContext servletContext) {
final String millisBetweenCleanUps = servletContext.getInitParameter(MILLIS_BETWEEN_CLEANUPS_INIT_PARAM);
if (millisBetweenCleanUps == null) {
return DEFAULT_MILLIS_BETWEEN_CLEANUPS;
}
try {
return Long.parseLong(millisBetweenCleanUps);
} catch (NumberFormatException exception) {
throw new RuntimeException("The servlet context-param " + MILLIS_BETWEEN_CLEANUPS_INIT_PARAM + " must be a valid number (hint: this is usually set in web.xml)", exception);
}
}
/**
* Does the actual clean up each time the timer goes off.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
private static final class Cleaner extends TimerTask {
private final CleanUpRegistry cleanUpRegistry;
private Cleaner(CleanUpRegistry cleanUpRegistry) {
this.cleanUpRegistry = cleanUpRegistry;
}
public void run() {
this.cleanUpRegistry.cleanAll();
}
}
}

View File

@ -0,0 +1,32 @@
package org.jasig.cas.client.cleanup;
/**
* A central location for all {@link Cleanable}s to register themselves.
* Then a thread or timer can occasionally call {@link #cleanAll()} to
* run the {@link Cleanable#cleanUp()} methods.
* <p>
* See {@link CleanUpListener} or {@link CleanUpJob} for two implementations
* of a timer which calls {@link #cleanAll()}. Either implementation will
* work, though you only need to choose one.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public interface CleanUpRegistry {
/**
* Adds a {@link Cleanable} to the list of objects whose
* {@link Cleanable#cleanUp()} method will be called.
*
* @param cleanable the {@link Cleanable} to add to the list. If a
* {@link Cleanable} is added twice, it's {@link Cleanable#cleanUp()}
* method will be called twice each time the timer goes off
* (this is probably not the desired outcome).
*/
public void addCleanble(Cleanable cleanable);
/**
* Runs {@link Cleanable#cleanUp()} on each {@link Cleanable}
* passed in to {@link #addCleanble(Cleanable)}
*/
public void cleanAll();
}

View File

@ -0,0 +1,47 @@
package org.jasig.cas.client.cleanup;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* An old-school singleton implementation of {@link CleanUpRegistry}.
* This implementation does not require clients to add any extra Spring
* configuration, hence the old-school singleton.
*
* A thread or timer should occasionally call {@link #cleanAll()} to
* run the {@link Cleanable#cleanUp()} method on each {@link Cleanable}.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public final class CleanUpRegistryImpl implements CleanUpRegistry {
private static CleanUpRegistryImpl cleanUpRegistry = new CleanUpRegistryImpl();
private List cleanables = Collections.synchronizedList(new ArrayList());
private CleanUpRegistryImpl() {
}
public static CleanUpRegistryImpl getInstance() {
return cleanUpRegistry;
}
/**
* {@inheritDoc}
*/
public void addCleanble(Cleanable cleanable) {
cleanables.add(cleanable);
}
/**
* {@inheritDoc}
*/
public void cleanAll() {
synchronized (cleanables) {
for (Iterator iterator = cleanables.iterator(); iterator.hasNext();) {
Cleanable cleanable = (Cleanable) iterator.next();
cleanable.cleanUp();
}
}
}
}

View File

@ -0,0 +1,20 @@
package org.jasig.cas.client.cleanup;
/**
* A simple interface representing an object which needs regular cleaning.
*
* @see CleanUpRegistry
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public interface Cleanable {
/**
* This method will be called on a regular basis
* to perform internal clean up (for example: removing
* old items from a cache).
* <p>
* Objects implementing this interface so they can be
* registered with the {@link CleanUpRegistry}.
*/
void cleanUp();
}

View File

@ -12,12 +12,15 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.cleanup.CleanUpRegistry;
import org.jasig.cas.client.cleanup.Cleanable;
/**
* Implementation of {@link ProxyGrantingTicketStorage} that is backed by a
* HashMap that keeps a ProxyGrantingTicket for a specified amount of time.
* <p/>
* A cleanup thread is run periodically to clean out the HashMap.
* <p>
* {@link CleanUpRegistry#cleanAll()} must be called on a regular basis to
* keep the HashMap from growing indefinitely.
*
* @author Scott Battaglia
* @author Brad Cupit (brad [at] lsu {dot} edu)
@ -25,7 +28,7 @@ import org.apache.commons.logging.LogFactory;
* @since 3.0
*/
public final class ProxyGrantingTicketStorageImpl implements
ProxyGrantingTicketStorage {
ProxyGrantingTicketStorage, Cleanable {
private final Log log = LogFactory.getLog(getClass());
@ -39,6 +42,14 @@ public final class ProxyGrantingTicketStorageImpl implements
*/
private final Map cache = Collections.synchronizedMap(new HashMap());
/**
* time, in milliseconds, before a {@link ProxyGrantingTicketHolder}
* is considered expired and ready for removal.
*
* @see ProxyGrantingTicketStorageImpl#DEFAULT_TIMEOUT
*/
private long timeout;
/**
* Constructor set the timeout to the default value.
*/
@ -53,10 +64,7 @@ public final class ProxyGrantingTicketStorageImpl implements
* @param timeout the time to hold on to the ProxyGrantingTicket
*/
public ProxyGrantingTicketStorageImpl(final long timeout) {
final Thread thread = new ProxyGrantingTicketCleanupThread(
timeout, this.cache);
thread.setDaemon(true);
thread.start();
this.timeout = timeout;
}
/**
@ -91,6 +99,25 @@ public final class ProxyGrantingTicketStorageImpl implements
this.cache.put(proxyGrantingTicketIou, holder);
}
/**
* Cleans up old, expired proxy tickets. This method should be
* called regularly via a thread or timer.
*
* @see CleanUpRegistry#cleanAll()
*/
public void cleanUp() {
synchronized (this.cache) {
for (final Iterator iter = this.cache.values().iterator(); iter
.hasNext();) {
final ProxyGrantingTicketHolder holder = (ProxyGrantingTicketHolder) iter.next();
if (holder.isExpired(this.timeout)) {
iter.remove();
}
}
}
}
private static final class ProxyGrantingTicketHolder {
private final String proxyGrantingTicket;
@ -110,39 +137,4 @@ public final class ProxyGrantingTicketStorageImpl implements
return System.currentTimeMillis() - this.timeInserted > timeout;
}
}
private static final class ProxyGrantingTicketCleanupThread extends Thread {
private final long timeout;
private final Map cache;
public ProxyGrantingTicketCleanupThread(final long timeout,
final Map cache) {
this.timeout = timeout;
this.cache = cache;
}
public void run() {
while (true) {
try {
Thread.sleep(this.timeout);
} catch (final InterruptedException e) {
// nothing to do
}
synchronized (this.cache) {
for (final Iterator iter = this.cache.values().iterator(); iter
.hasNext();) {
final ProxyGrantingTicketHolder holder = (ProxyGrantingTicketHolder) iter.next();
if (holder.isExpired(this.timeout)) {
iter.remove();
}
}
}
}
}
}
}

View File

@ -5,10 +5,13 @@
*/
package org.jasig.cas.client.validation;
import org.jasig.cas.client.proxy.Cas20ProxyRetriever;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
import org.jasig.cas.client.util.CommonUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@ -17,13 +20,14 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jasig.cas.client.cleanup.CleanUpRegistry;
import org.jasig.cas.client.cleanup.CleanUpRegistryImpl;
import org.jasig.cas.client.cleanup.Cleanable;
import org.jasig.cas.client.proxy.Cas20ProxyRetriever;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
import org.jasig.cas.client.util.CommonUtils;
/**
* Creates either a CAS20ProxyTicketValidator or a CAS20ServiceTicketValidator depending on whether any of the
@ -46,10 +50,15 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal
*/
private String proxyReceptorUrl;
/**
* a place to register implementations of {@link Cleanable}
*/
private CleanUpRegistry cleanUpRegistry;
/**
* Storage location of ProxyGrantingTickets and Proxy Ticket IOUs.
*/
private ProxyGrantingTicketStorage proxyGrantingTicketStorage = new ProxyGrantingTicketStorageImpl();
private ProxyGrantingTicketStorage proxyGrantingTicketStorage;
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
super.initInternal(filterConfig);
@ -59,6 +68,13 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal
public void init() {
super.init();
if (this.cleanUpRegistry == null) {
this.cleanUpRegistry = CleanUpRegistryImpl.getInstance();
}
this.proxyGrantingTicketStorage = newProxyGrantingTicketStorage(this.cleanUpRegistry);
CommonUtils.assertNotNull(this.proxyGrantingTicketStorage, "proxyGrantingTicketStorage cannot be null.");
}
@ -136,7 +152,14 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal
this.proxyReceptorUrl = proxyReceptorUrl;
}
public final void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
protected final void setCleanUpRegistry(final CleanUpRegistry cleanUpRegistry) {
this.cleanUpRegistry = cleanUpRegistry;
}
private ProxyGrantingTicketStorage newProxyGrantingTicketStorage(final CleanUpRegistry cleanUpRegistry) {
ProxyGrantingTicketStorageImpl proxyGrantingTicketStorageImpl = new ProxyGrantingTicketStorageImpl();
cleanUpRegistry.addCleanble(proxyGrantingTicketStorageImpl);
return proxyGrantingTicketStorageImpl;
}
}

View File

@ -0,0 +1,30 @@
package org.jasig.cas.client.cleanup;
import org.jasig.cas.client.util.MethodFlag;
import junit.framework.TestCase;
/**
* Unit test for {@link CleanUpJob}
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class CleanUpJobTest extends TestCase {
public void testExecuteInternal() throws Exception {
final MethodFlag cleanAllMethodFlag = new MethodFlag();
CleanUpRegistry localCleanUpRegistry = new CleanUpRegistry() {
public void addCleanble(Cleanable cleanable) {
}
public void cleanAll() {
cleanAllMethodFlag.setCalled();
}
};
final CleanUpJob cleanUpJob = new CleanUpJob(localCleanUpRegistry);
cleanUpJob.executeInternal(null);
assertTrue(cleanAllMethodFlag.wasCalled());
}
}

View File

@ -0,0 +1,160 @@
package org.jasig.cas.client.cleanup;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import junit.framework.TestCase;
import org.jasig.cas.client.util.MethodFlag;
import org.springframework.mock.web.MockServletContext;
/**
* Unit test for {@link CleanUpListener}
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class CleanUpListenerTest extends TestCase {
private final CleanUpRegistry defaultCleanUpRegistry = CleanUpRegistryImpl.getInstance();
private final Timer defaultTimer = new Timer(true);
public void testStartsThreadAtStartup() throws Exception {
final MethodFlag scheduleMethodFlag = new MethodFlag();
final Timer timer = new Timer(true) {
public void schedule(TimerTask task, long delay, long period) {
scheduleMethodFlag.setCalled();
}
};
final CleanUpListener cleanUpListener = new CleanUpListener(timer, defaultCleanUpRegistry);
cleanUpListener.contextInitialized(new TestServletContextEvent(1));
assertTrue(scheduleMethodFlag.wasCalled());
}
public void testShutsDownTimerThread() throws Exception {
final MethodFlag cancelMethodFlag = new MethodFlag();
final Timer timer = new Timer(true) {
public void cancel() {
cancelMethodFlag.setCalled();
super.cancel();
}
};
final CleanUpListener cleanUpListener = new CleanUpListener(timer, defaultCleanUpRegistry);
cleanUpListener.contextInitialized(new TestServletContextEvent(1));
cleanUpListener.contextDestroyed(null);
assertTrue(cancelMethodFlag.wasCalled());
}
public void testCallsCleanAllOnSchedule() throws Exception {
final MethodFlag cleanAllMethodFlag = new MethodFlag();
final CleanUpRegistry cleanUpRegistry = new CleanUpRegistry() {
public void addCleanble(Cleanable cleanable) {
}
public void cleanAll() {
cleanAllMethodFlag.setCalled();
}
};
long millisBetweenCleanUps = 250;
final CleanUpListener cleanUpListener = new CleanUpListener(defaultTimer, cleanUpRegistry);
cleanUpListener.contextInitialized(new TestServletContextEvent(millisBetweenCleanUps));
// wait long enough for the clean up to occur
Thread.sleep(millisBetweenCleanUps * 2);
assertTrue(cleanAllMethodFlag.wasCalled());
}
public void testDelaysFirstCleanAll() throws Exception {
final MethodFlag cleanAllMethodFlag = new MethodFlag();
final CleanUpRegistry cleanUpRegistry = new CleanUpRegistry() {
public void addCleanble(Cleanable cleanable) {
}
public void cleanAll() {
cleanAllMethodFlag.setCalled();
}
};
long millisBetweenCleanUps = 250;
final CleanUpListener cleanUpListener = new CleanUpListener(defaultTimer, cleanUpRegistry);
cleanUpListener.contextInitialized(new TestServletContextEvent(millisBetweenCleanUps));
assertFalse(cleanAllMethodFlag.wasCalled());
// wait long enough for the clean up to occur
Thread.sleep(millisBetweenCleanUps * 2);
assertTrue(cleanAllMethodFlag.wasCalled());
}
public void testReturnsDefaultWhenNoContextParamConfigured() throws Exception {
final ServletContext servletContext = new MockServletContext();
long millisBetweenCleanups = new CleanUpListener().getMillisBetweenCleanups(servletContext);
assertEquals(CleanUpListener.DEFAULT_MILLIS_BETWEEN_CLEANUPS, millisBetweenCleanups);
}
public void testFailsWithInvalidNumber() throws Exception {
final ServletContext servletContext = new MockServletContext() {
public String getInitParameter(String name) {
if (name.equals(CleanUpListener.MILLIS_BETWEEN_CLEANUPS_INIT_PARAM)) {
return "not a number";
} else {
return null;
}
}
};
try {
new CleanUpListener().getMillisBetweenCleanups(servletContext);
fail("expected an exception");
} catch (RuntimeException e) {
// expected, test passes
}
}
/**
* A unit test helper class to mock a real {@link ServletContextEvent}
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
private static final class TestServletContextEvent extends ServletContextEvent {
private TestServletContextEvent(long millisBetweenCleanUps) {
super(new TestServletContext(millisBetweenCleanUps));
}
}
/**
* A unit test helper class to mock a real {@link ServletContext}
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
private static final class TestServletContext extends MockServletContext {
private final long millisBetweenCleanUps;
public TestServletContext(long millisBetweenCleanUps) {
this.millisBetweenCleanUps = millisBetweenCleanUps;
}
public String getInitParameter(String name) {
if (name.equals(CleanUpListener.MILLIS_BETWEEN_CLEANUPS_INIT_PARAM)) {
return Long.toString(millisBetweenCleanUps);
} else {
throw new RuntimeException("Unexpected init param requested: " + name);
}
}
}
}

View File

@ -0,0 +1,28 @@
package org.jasig.cas.client.cleanup;
import org.jasig.cas.client.util.MethodFlag;
import junit.framework.TestCase;
/**
* Unit test for {@link CleanUpRegistryImpl}
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class CleanUpRegistryImplTest extends TestCase {
private final CleanUpRegistry cleanUpRegistry = CleanUpRegistryImpl.getInstance();
public void testCleanAll() throws Exception {
final MethodFlag cleanUpMethodFlag = new MethodFlag();
cleanUpRegistry.addCleanble(new Cleanable() {
public void cleanUp() {
cleanUpMethodFlag.setCalled();
}
});
cleanUpRegistry.cleanAll();
assertTrue(cleanUpMethodFlag.wasCalled());
}
}

View File

@ -0,0 +1,24 @@
package org.jasig.cas.client.proxy;
import junit.framework.TestCase;
/**
* Unit test for {@link ProxyGrantingTicketStorageImpl}
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class ProxyGrantingTicketStorageImplTest extends TestCase {
public void testCleanUp() throws Exception {
String proxyGrantingTicketIou = "proxyGrantingTicketIou";
int timeout = 100;
ProxyGrantingTicketStorageImpl storage = new ProxyGrantingTicketStorageImpl(timeout);
storage.save(proxyGrantingTicketIou, "proxyGrantingTicket");
Thread.sleep(timeout + 1);
storage.cleanUp();
assertNull(storage.retrieve(proxyGrantingTicketIou));
}
}

View File

@ -0,0 +1,22 @@
package org.jasig.cas.client.util;
/**
* A mutable boolean-like flag for unit tests which use
* anonymous classes.
* <p>
* A simple boolean would be ideal, except Java requires us
* to mark enclosing local variables as final.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class MethodFlag {
boolean called = false;
public boolean wasCalled() {
return called;
}
public void setCalled() {
called = true;
}
}

View File

@ -0,0 +1,57 @@
package org.jasig.cas.client.validation;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.jasig.cas.client.cleanup.CleanUpRegistry;
import org.jasig.cas.client.cleanup.Cleanable;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
/**
* Unit test for {@link Cas20ProxyReceivingTicketValidationFilter}
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
public class Cas20ProxyReceivingTicketValidationFilterTest extends TestCase {
public void testRegistersPGTicketStorageWithCleanUpRegistry() throws Exception {
final TestCleanUpRegistry cleanUpRegistry = new TestCleanUpRegistry();
final Cas20ProxyReceivingTicketValidationFilter filter = newCas20ProxyReceivingTicketValidationFilter();
filter.setCleanUpRegistry(cleanUpRegistry);
filter.init();
assertEquals(1, cleanUpRegistry.getCleanables().size());
assertSame(ProxyGrantingTicketStorageImpl.class, cleanUpRegistry.getCleanables().get(0).getClass());
}
private Cas20ProxyReceivingTicketValidationFilter newCas20ProxyReceivingTicketValidationFilter() {
final Cas20ProxyReceivingTicketValidationFilter filter = new Cas20ProxyReceivingTicketValidationFilter();
filter.setServerName("localhost");
filter.setTicketValidator(new Cas20ProxyTicketValidator(""));
return filter;
}
/**
* A test implementation of {@link CleanUpRegistry} that allows us to see
* which {@link Cleanable}s were registered.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
*/
private static final class TestCleanUpRegistry implements CleanUpRegistry {
private final List cleanables = new ArrayList();
public void addCleanble(Cleanable cleanable) {
cleanables.add(cleanable);
}
public void cleanAll() {
}
public List getCleanables() {
return cleanables;
}
};
}