That is just what we are doing:
Tracking performance,
Caching (for quicker development)
And also to block the UI with a nice looking dialog with a progress bar every time there is a blocking EJB call in progress.
Basically, we wrap the remote interfaces by using dynamic proxies.
See if these few classes are any inspiration. I can send you a zipped version of this, if you need it write to me at 'pablo at anahatait doot com'
package com.anahata.jee.client.ejb.wrapper;
import com.anahata.salute.Main;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Remote;
/**
*
* @author Pablito
* @created Jul 12, 2010
*
*/
public class RemoteInferfaceWrapper {
private static final Logger log = Logger.getLogger(RemoteInferfaceWrapper.class.getName());
public static void wrapRemoteInterfaces(Class appClientMainClass, EjbCallListener ejbCallListener) throws IllegalArgumentException, IllegalAccessException {
for (Field f : appClientMainClass.getDeclaredFields()) {
log.log(Level.INFO, "RemoteInfefaceWrapper Processing field {0}", f);
//App-client main class dependency injection happens only on static fields annotated with @EJB
//whose type is an interface. The interface~ doesnt neccesary have to carry the @Remote annotation
if (Modifier.isStatic(f.getModifiers()) && f.getType().isInterface() && f.getType().isAnnotationPresent(Remote.class)) {
boolean isAccesible = f.isAccessible();
f.setAccessible(true);
Object stub = f.get(null);
f.setAccessible(isAccesible);
System.out.println("Creating wrapper for interface " + f.getType());
stub = wrapEjbCallMonitor(stub, new Class[]{f.getType()}, ejbCallListener);
if (Main.getSaluteApplication().isDev()) {
//stub = wrapWithCache(stub, new Class[]{f.getType()});
stub = wrapWithPerformanceMonitor(stub, new Class[]{f.getType()});
}
f.setAccessible(true);//in case it is private
f.set(null, stub);
f.setAccessible(isAccesible);
}
}
}
public static Object wrapWithCache(Object toBeWrapped, Class[] interfaces) {
ClassLoader cl = toBeWrapped != null ? toBeWrapped.getClass().getClassLoader() : interfaces[0].getClassLoader();
InvocationHandler ic = new CachingInvocationHandler(toBeWrapped, interfaces);
Object ret = Proxy.newProxyInstance(
cl,
interfaces,
ic);
log.log(Level.INFO, "Creating dev only caching proxy for {0} Generated proxy is: {1}, InvocationHandler is{2}", new Object[]{toBeWrapped, ret, ic });
return ret;
}
/**
* To be used for dev only
*
* @param toBeWrapped
* @param interfaces
* @return
*/
public static Object wrapWithPerformanceMonitor(Object toBeWrapped, Class[] interfaces) {
ClassLoader cl = toBeWrapped != null ? toBeWrapped.getClass().getClassLoader() : interfaces[0].getClassLoader();
Object ret = Proxy.newProxyInstance(
cl,
interfaces,
new PerformanceInvocationHandler(toBeWrapped, interfaces));
log.log(Level.INFO, "Created performance monitor for for {0}. Generated Proxy is: {1}", new Object[]{toBeWrapped, ret});
return ret;
}
public static Object wrapEjbCallMonitor(Object toBeWrapped, Class[] interfaces, EjbCallListener listener) {
ClassLoader cl = toBeWrapped != null ? toBeWrapped.getClass().getClassLoader() : interfaces[0].getClassLoader();
Object ret = Proxy.newProxyInstance(
cl,
interfaces,
new EjbCallNotifierInvocationHandler(interfaces[0], toBeWrapped, listener));
log.log(Level.INFO, "EjbCallNotifier for {0}. Generated Proxy is: {1}", new Object[]{toBeWrapped, ret});
return ret;
}
}
/*
*
*
*/
package com.anahata.jee.client.ejb.wrapper;
import com.anahata.salute.ejb.interceptor.DevOnlyCacheable;
import com.anahata.salute.ejb.interceptor.DevOnlyCachingInterceptor;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Pablito
* @created Jul 23, 2010
*
*/
public class CachingInvocationHandler implements InvocationHandler {
private static final Logger log = Logger.getLogger(CachingInvocationHandler.class.getName());
Object wrappedObject;
Class[] interfaces;
public CachingInvocationHandler(Object wrappedObject, Class[] interfaces) {
this.wrappedObject = wrappedObject;
this.interfaces = interfaces;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = null;
if (method.isAnnotationPresent(DevOnlyCacheable.class)
|| method.getDeclaringClass().isAnnotationPresent(DevOnlyCacheable.class)) {
File f = DevOnlyCachingInterceptor.getStorageFileName(method, args);
if (f.exists()) {
try {
ret = DevOnlyCachingInterceptor.retrieveCachedMethodCall(method, f);
} catch (Throwable t) {
log.log(Level.INFO, "Caching interceptor could not unserialize cached response, exception is ", t);
ret = method.invoke(wrappedObject, args);
DevOnlyCachingInterceptor.storeMethodReturn(method, ret, f);
}
} else {
ret = method.invoke(wrappedObject, args);
DevOnlyCachingInterceptor.storeMethodReturn(method, ret, f);
}
} else {
ret = method.invoke(wrappedObject, args);
}
return ret;
}
}
/*
*
*
*/
package com.anahata.jee.client.ejb.wrapper;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Pablito
* @created Jul 23, 2010
*
*/
public class PerformanceInvocationHandler implements InvocationHandler{
private static final Logger log = Logger.getLogger(PerformanceInvocationHandler.class.getName());
Object wrappedObject;
Class[] interfaces;
public PerformanceInvocationHandler(Object wrappedObject, Class[] interfaces) {
this.wrappedObject = wrappedObject;
this.interfaces = interfaces;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (Class c : interfaces) {
for (Method m : c.getMethods()) {
if (m.equals(method)) {
System.out.println("------------call");
log.log(Level.INFO, "Performance monitor: about to call : {0}.{1} with arguments: {2}", new Object[]{wrappedObject.getClass().getSimpleName(), method.getName(), Arrays.deepToString(args)});
long ts = System.currentTimeMillis();
Object ret = method.invoke(wrappedObject, args);
ts = System.currentTimeMillis() - ts;
log.log(Level.INFO, "Performance monitor: \n\t{0}.{1} with arguments: {2} \n\t-->{3} ms.", new Object[]{wrappedObject.getClass().getSimpleName(), method.getName(), Arrays.deepToString(args), ts});
return ret;
}
}
}
return method.invoke(wrappedObject, args); //no performance wrapping
}
}
/*
*
*
*/
package com.anahata.jee.client.ejb.wrapper;
import com.anahata.salute.client.ui.EJBCallBlockingDialog;
import com.anahata.salute.ejb.interceptor.CallDescription;
import com.anahata.salute.ejb.interceptor.NonBlocking;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import javax.ejb.ApplicationException;
import javax.swing.JDialog;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
/**
* Intercepts calls to EJBs. If the EJB method is annotated with @NonBlocking,
* this handler will notify EjbCallListener.
*
*
* and notifies an EjbCallListener that the call
*
* @author Pablito
* @created Jul 23, 2010
*
*/
public class EjbCallNotifierInvocationHandler implements InvocationHandler {
private static final Logger log = Logger.getLogger(EjbCallNotifierInvocationHandler.class.getName());
Object wrappedObject;
EjbCallListener edtListener;
Class wrappedInterface;
public EjbCallNotifierInvocationHandler(Class wrappedInterface, Object wrappedObject, final EjbCallListener listener) {
this.wrappedInterface = wrappedInterface;
this.wrappedObject = wrappedObject;
this.edtListener = (EjbCallListener) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), listener.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
method.invoke(listener, args);
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
});
return null;
}
});
}
private boolean isInterceptableMethod(Method method) {
try {
if (wrappedInterface.getDeclaredMethod(method.getName(), method.getParameterTypes()) == null) {
return false;//It is calling toString() or hashCode or something outside the interface we wanted to wrap.
}
} catch (NoSuchMethodException e) {
return false;//It is calling toString() or hashCode or something outside the interface we wanted to wrap.
}
return true;
}
private boolean isBlocking(Method method) {
if (method.isAnnotationPresent(NonBlocking.class) || method.getClass().isAnnotationPresent(NonBlocking.class)) {
return false;
}
if (!isInterceptableMethod(method)) {
return false;
}
return true;
}
public class CustomSwingWorker extends SwingWorker {
JDialog d;
Method method;
Object[] args;
long id;
String desc;
public CustomSwingWorker(long id, String desc, Method m, Object[] args) {
d = new EJBCallBlockingDialog(desc);
addPropertyChangeListener(new SwingWorkerCompletionWaiter(d));
this.method = m;
this.args = args;
this.desc = desc;
}
@Override
protected void done() {
super.done();
}
@Override
protected Object doInBackground() throws Exception {
System.out.println("About to call ejb, notifying listener of " + desc);
//listener.callWillBegin(id, desc);
System.out.println("listener notified ");
Object ret = null;
try {
System.out.println("about to invoke on ejb stub " + method.toGenericString());
ret = method.invoke(wrappedObject, args);
System.out.println("invokation finished without exception ");
d.setVisible(false);
} catch (Throwable t) {
throw t instanceof Exception ? ((Exception) t) : new Exception(t);
}
return ret;
}
public Object getExecute() throws Throwable {
execute();
d.setVisible(true);
try {
return super.get();
} catch (ExecutionException e) {
Throwable t = e;
d.setVisible(false);
t.printStackTrace(System.err);
if (getApplicationException(t) != null) {
throw getApplicationException(t);
} else {
edtListener.callThrewException(id, desc, e, true);
throw t;
}
}
}
}
private static Throwable getApplicationException(Throwable t) {
do {
if (t.getClass().isAnnotationPresent(ApplicationException.class)) {
return t;
}
t = t.getCause();
} while (t != null);
return null;
}
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
final long id = System.currentTimeMillis();
CallDescription anot = method.getAnnotation(CallDescription.class);
final String desc = anot != null ? anot.value() : "Connected to Squirrel server...";
Object ret = null;
if (isBlocking(method)) {
CustomSwingWorker sw = new CustomSwingWorker(id, desc, method, args);
ret = sw.getExecute();
} else if (isInterceptableMethod(method)){
NonBlocking nb = method.getAnnotation(NonBlocking.class);
//Is non blocking, we just want to
if (nb != null && nb.notifiesCallWillBegin()) {
edtListener.callWillBegin(id, desc);
}
try {
ret = method.invoke(wrappedObject, args);
if (nb != null && nb.notifiesCallEnded()) {
edtListener.callEnded(id, desc, ret);
}
} catch (Throwable t) {
t.printStackTrace(System.err);
if (nb != null && nb.notifiesCallEnded()) {
edtListener.callEnded(id, desc, ret);
}
if (getApplicationException(t) != null) {
throw getApplicationException(t);
} else {
if (nb != null && nb.notifiesCallThrewException()) {
edtListener.callThrewException(id, desc, t, true);
throw t;
}
}
}
} else {
ret = method.invoke(wrappedObject, args);
}
return ret;
}
private static class SwingWorkerCompletionWaiter implements PropertyChangeListener {
private JDialog dialog;
public SwingWorkerCompletionWaiter(JDialog dialog) {
this.dialog = dialog;
}
public void propertyChange(PropertyChangeEvent event) {
if ("state".equals(event.getPropertyName())
&& SwingWorker.StateValue.DONE == event.getNewValue()) {
dialog.setVisible(false);
dialog.dispose();
}
}
}
}
[Message sent by forum member 'pablopina']
http://forums.java.net/jive/thread.jspa?messageID=486046