diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index eb28d0e..fbe33e3 100644 --- a/cas-client-core/pom.xml +++ b/cas-client-core/pom.xml @@ -101,6 +101,22 @@ test + + org.springframework + spring-context-support + ${spring.version} + compile + true + + + + opensymphony + quartz-all + 1.6.0 + compile + true + + log4j log4j diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpJob.java b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpJob.java new file mode 100644 index 0000000..e684a9e --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpJob.java @@ -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 + *

+ * {@link CleanUpListener}, the other implementation, does require + * changes to web.xml, but does not require any Spring configuration. + *

+ * The decision as to which timer implementation you use is entirely + * subjective, as both perform the same operation. + *

+ * Example Spring config: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * 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(); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpListener.java b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpListener.java new file mode 100644 index 0000000..bc2cdf3 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpListener.java @@ -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. + *

+ * {@link CleanUpJob}, the other implementation, does require + * Spring, but does not require changes to web.xml + *

+ * The decision as to which timer implementation you use is entirely + * subjective, as both perform the same operation. + *

+ * You can customize the time between cleanup runs by setting a + * context-param in web.xml + *

+ * Example web.xml config: + * + * + * millisBetweenCleanUps + * 45000 + * + * + * + * 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(); + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpRegistry.java b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpRegistry.java new file mode 100644 index 0000000..dbabd39 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpRegistry.java @@ -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. + *

+ * 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(); +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpRegistryImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpRegistryImpl.java new file mode 100644 index 0000000..4970e8a --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/CleanUpRegistryImpl.java @@ -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(); + } + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/Cleanable.java b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/Cleanable.java new file mode 100644 index 0000000..f4bdaaf --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/cleanup/Cleanable.java @@ -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). + *

+ * Objects implementing this interface so they can be + * registered with the {@link CleanUpRegistry}. + */ + void cleanUp(); +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java index f4d6621..dc3f62e 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java @@ -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. - *

- * A cleanup thread is run periodically to clean out the HashMap. + *

+ * {@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(); - } - } - } - } - } - } } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java index 858dd58..9a5e0ee 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java @@ -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; } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpJobTest.java b/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpJobTest.java new file mode 100644 index 0000000..015bee6 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpJobTest.java @@ -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()); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpListenerTest.java b/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpListenerTest.java new file mode 100644 index 0000000..7e23c50 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpListenerTest.java @@ -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); + } + } + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpRegistryImplTest.java b/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpRegistryImplTest.java new file mode 100644 index 0000000..6eab4c4 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/cleanup/CleanUpRegistryImplTest.java @@ -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()); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImplTest.java b/cas-client-core/src/test/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImplTest.java new file mode 100644 index 0000000..7eb2565 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImplTest.java @@ -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)); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/util/MethodFlag.java b/cas-client-core/src/test/java/org/jasig/cas/client/util/MethodFlag.java new file mode 100644 index 0000000..e49e084 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/util/MethodFlag.java @@ -0,0 +1,22 @@ +package org.jasig.cas.client.util; + +/** + * A mutable boolean-like flag for unit tests which use + * anonymous classes. + *

+ * 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; + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilterTest.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilterTest.java new file mode 100644 index 0000000..e79c3f6 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilterTest.java @@ -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; + } + }; +}