springspring-remoting

Spring Remoting HTTP invoker - exception handling


I'm using Spring's 'HTTP Invoker' remoting solution to expose DAOs to many different applications, but have all database access in a single server.

This works well, but if the server throws, say, a HibernateSystemException, Spring serializes that and sends it over the wire back to the client. That doesn't work because the client doesn't (and shouldn't) have HibernateSystemException in its classpath.

Might there be a way to have Spring Remoting wrap my exception in something that I specify that would be common between client and server to avoid issues like this?

I know that I could do that in my server code by wrapping everything the DAO does in a try/catch, but that's admittedly sloppy.

Thanks, Roy


Solution

  • I ran into this issue as well; I am exposing a service via HTTP Invoker that accesses a database using Spring 3.1, JPA 2, and Hibernate as the JPA provider.

    To work around the problem, I wrote a custom Interceptor and an exception called WrappedException. The interceptor catches exceptions thrown by the service and converts the exceptions and causes to WrappedException using reflection and setters. Assuming the client has the WrappedException on its class path, the stack trace and original exception class names are visible to the client.

    This relaxes the need for the client to have Spring DAO on its class path and as far as I can tell, no original stack trace information is lost in the translation.

    Interceptor

    public class ServiceExceptionTranslatorInterceptor implements MethodInterceptor, Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            try {
                return invocation.proceed();
            } catch (Throwable e) {
                throw translateException(e);
            }
        }
    
        static RuntimeException translateException(Throwable e) {
            WrappedException serviceException = new WrappedException();
    
            try {
                serviceException.setStackTrace(e.getStackTrace());
                serviceException.setMessage(e.getClass().getName() +
                        ": " + e.getMessage());
                getField(Throwable.class, "detailMessage").set(serviceException, 
                        e.getMessage());
                Throwable cause = e.getCause();
                if (cause != null) {
                    getField(Throwable.class, "cause").set(serviceException,
                            translateException(cause));
                }
            } catch (IllegalArgumentException e1) {
                // Should never happen, ServiceException is an instance of Throwable
            } catch (IllegalAccessException e2) {
                // Should never happen, we've set the fields to accessible
            } catch (NoSuchFieldException e3) {
                // Should never happen, we know 'detailMessage' and 'cause' are
                // valid fields
            }
            return serviceException;
        }
    
        static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
            Field f = clazz.getDeclaredField(fieldName);
            if (!f.isAccessible()) {
                f.setAccessible(true);
            }
            return f;
        }
    
    }
    

    Exception

    public class WrappedException extends RuntimeException {
    
        private static final long serialVersionUID = 1L;
    
        private String message = null;
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        @Override
        public String toString() {
            return message;
        }
    }
    

    Bean Wiring

    <bean id="exceptionTranslatorInterceptor" class="com.YOURCOMPANY.interceptor.ServiceExceptionTranslatorInterceptor"/>
    
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="YOUR_SERVICE" />
        <property name="order" value="1" />
        <property name="interceptorNames">
            <list>
                <value>exceptionTranslatorInterceptor</value>
            </list>
        </property>
    </bean>