Listing 1: Initial ServiceFactory implementation
package service;
import java.lang.reflect.Proxy;
public class ServiceFactory {
public Service newEchoService() {
return (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
new CacheProxy(new EchoService()));
}
}
Listing 2: Initial CacheProxy implementation
package service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class CacheProxy implements InvocationHandler {
private final Object obj;
public CacheProxy(Object toProxy) {
this.obj = toProxy;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(obj, args);
}
}
Listing 3: SimpleCache, HashMap implementation of a Cache
package service;
import java.util.HashMap;
import java.util.Map;
public class SimpleCache implements Cache {
private Map values;
public SimpleCache() {
values = new HashMap(16);
}
public Object retrieve(Object key) {
return values.get(key);
}
public void store(Object key, Object value) {
values.put(key, value);
}
}
Listing 4: Final implementation of CacheProxy
package service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
public class CacheProxy implements InvocationHandler {
private static final Object NullKey = new Object();
private final Object obj;
private Cache caches;
public CacheProxy(Object toProxy) {
this.obj = toProxy;
caches = new SimpleCache();
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Cache cache = findMethodCache(method);
Object key = generateArgumentKey(args);
Object value = cache.retrieve(key);
if (value == null)
cache.store(key, value = method.invoke(obj, args));
return value;
}
private Cache findMethodCache(Method method) {
Cache cache = (Cache) caches.retrieve(method);
if (cache == null)
caches.store(method, cache = new SimpleCache());
return cache;
}
private Object generateArgumentKey(Object[] args) {
return args != null ? Arrays.asList(args) : NullKey;
}
}
Listing 5: Time-to-live shell implementation
package service;
public class TimeToLiveCache
implements Runnable, Cache {
private final int ttlSeconds;
private Thread ttlThread;
public TimeToLiveCache(int ttlSeconds) {
this.ttlSeconds = ttlSeconds;
ttlThread = new Thread(this);
ttlThread.start();
}
public void run() {
/* wake up and remove entries
* every so often
*/
}
public Object retrieve(Object key) {
/* basic retrieval */
}
public void store(Object key, Object value) {
/* basic storage, but will also need to
* record the time the entry was added
*/
}
}
Listing 6: Test cases and supporting test code
package service;
import service.CacheProxy;
import service.Service;
import service.ServiceFactory;
import junit.framework.TestCase;
public class ServiceTest extends TestCase {
public void testNewEchoService() throws Exception {
Object toService = new Object();
Service foo = new ServiceFactory().newEchoService();
assertEquals(toService, foo.service(toService));
}
public void testSingulizedServices() throws Exception {
ServiceFactory serviceFactory = new ServiceFactory();
Service svc = serviceFactory.newEchoService();
assertNotNull(svc);
assertSame(svc, serviceFactory.newEchoService());
}
public void testCacheProxy() throws Throwable {
Integer expectedInteger = new Integer(1);
MockService service = new MockService();
service.addExpected(³(service)²);
CacheProxy proxy = new CacheProxy(service);
for (int i = 0; i < 5; i++)
assertEquals(expectedInteger, proxy.invoke(null,
service.getClass().getMethod(³service²,
new Class[]{Object.class}), new Object[]{new Integer(1)}));
service.validate();
}
public void testCacheProxyNullArgs() throws Throwable {
MockService service = new MockService();
service.addExpected(³(doSomething)²);
CacheProxy proxy = new CacheProxy(service);
assertEquals(³did something², proxy.invoke(null,
service.getClass().getMethod(³doSomething²,
new Class[]{}), null));
service.validate();
}
public void testCacheByMethod() throws Throwable {
MockService service = new MockService();
service.addExpected(³(doSomething)²);
service.addExpected(³(doSomethingElse)²);
CacheProxy proxy = new CacheProxy(service);
assertEquals(³did something², proxy.invoke(null,
service.getClass().getMethod(³doSomething²,
new Class[]{}), null));
assertEquals(³did something else², proxy.invoke(null,
service.getClass().getMethod(
³doSomethingElse², new Class[]{}), null));
service.validate();
}
}
package service;
import junit.framework.Assert;
public class Mock {
private StringBuffer expected, actual;
public Mock() {
expected = new StringBuffer();
actual = new StringBuffer();
}
public void addExpected(String msg) {
expected.append(msg);
}
protected void addActual(String msg) {
actual.append(msg);
}
public void validate() {
Assert.assertEquals(expected.toString(), actual.toString());
}
}
package service;
import service.Service;
public class MockService extends Mock implements Service {
public Object service(Object toService) {
addActual(³(service)²);
return toService;
}
public String doSomething() {
addActual(³(doSomething)²);
return ³did something²;
}
public String doSomethingElse() {
addActual(³(doSomethingElse)²);
return ³did something else²;
}
}