Additional Code - zip file 6 KB

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²;
  }
}

Additional Code - zip file 6 KB