Hi,

In our current project we need to provide a simple (stand alone POJO) service
with high availability guarantees. The simplest way (for us) is to install it
on multiple servers and build a failover mechanism into the clients.
I was pleasantly surprised by how easy it is to achieve a transparent failover
using Spring, and I thought that this could be useful for others too.

Our first version below, is very basic: it always tries the first service
provider in the list, and if an exception is thrown, the next one is tried,
etc.
Possible improvements are:
- first try the provider that previously worked
- use round robin for basic load balancing

All comments are welcome,

Ivo

Code:
<bean id="highAvailabilityBean"
class="org.springframework.remoting.support.FailoverProxyFactoryBean">
          <property name="serviceInterface" value="com.x.MyInterface" />
          <property name="serviceProviders">
                  <list>
                  <!-- service beans are typically proxies to remote services  -->
                          <ref bean="primaryServiceProvider"/>
                          <ref bean="fallBackServiceProvider"/>
                  </list>
          </property>
</bean>
Code:
package org.springframework.remoting.support;


import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.remoting.support.RemoteAccessor;

/**
 * Factory bean for proxies to redundant services. Behaves like the proxied
 * service when used as bean reference, exposing the specified service
 * interface, but with transparent fail-over. All call to the proxy are
 * delegated to the first object in the list of service providers (@see
 * #setServiceProviders(List)). When this delegated call fails, the next
 * provider in the list will be called, etc.
 *
 * @author be324288
 *
 */
public class FailoverProxyFactoryBean extends RemoteAccessor implements
MethodInterceptor,
        InitializingBean, FactoryBean {

    private Object serviceProxy;

    private List serviceProviders;

    /**
     * The list of (redundant) objects that implement the interface specified
     * with {@link RemoteAccessor#setServiceInterface(java.lang.Class)}
     *
     * @return
     */
    public List getServiceProviders() {
        return serviceProviders;
    }

    /**
     * @param serviceProviders
     */
    public void setServiceProviders(List serviceProviders) {
        this.serviceProviders = serviceProviders;
    }

    public void afterPropertiesSet() throws Exception {
        if (getServiceInterface() == null) {
            throw new IllegalArgumentException("serviceInterface is required");
        }
        if (serviceProviders == null || serviceProviders.isEmpty()) {
            throw new IllegalArgumentException("serviceBeans is required");
        }
        for (Object o : serviceProviders) {
            if (!getServiceInterface().isInstance(o)) {
                throw new IllegalArgumentException(o.getClass()
                        + " does not implement the serviceInterface: " +
getServiceInterface());
            }
        }
        this.serviceProxy = ProxyFactory.getProxy(getServiceInterface(), this);
    }

    public Object getObject() {
        return this.serviceProxy;
    }

    public Class getObjectType() {
        return getServiceInterface();
    }

    public boolean isSingleton() {
        return true;
    }

    public Object invoke(MethodInvocation mi) throws Throwable {
        Iterator iter = serviceProviders.iterator();
        while (iter.hasNext()) {
            Object bean = iter.next();
            try {
                return bean.getClass().getMethod(mi.getMethod().getName(),
                        (Class[])
mi.getMethod().getParameterTypes()).invoke(bean,
                        mi.getArguments());
            } catch (InvocationTargetException e) {
                // try next bean or throw exception if this is the last bean
                if (!iter.hasNext()) {
                    throw e.getCause();
                }
            }
        }
        return null;
    }

}