users@glassfish.java.net

Re: Intercepting Remote EJB invocations on client side?

From: <glassfish_at_javadesktop.org>
Date: Sat, 23 Oct 2010 12:07:29 PDT

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