package cn.com.qmth.stmms.common.hessian;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.ConnectException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteConnectFailureException;
import org.springframework.remoting.RemoteProxyFailureException;
import org.springframework.remoting.support.RemoteAccessor;

import com.caucho.hessian.HessianException;
import com.caucho.hessian.client.HessianConnectionException;
import com.caucho.hessian.client.HessianRuntimeException;

public class CombineHessianClientInterceptor extends RemoteAccessor implements MethodInterceptor {

    @SuppressWarnings("rawtypes")
    private HessianClientRouter clientRouter;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            return tryInvoke(invocation, clientRouter.getClient(getServiceInterface(), invocation));
        } catch (IllegalClientKeyException e1) {
            throw new RuntimeException(e1);
        } catch (Exception e) {
            return tryInvoke(invocation, clientRouter.refreshClient(getServiceInterface()));
        }
    }

    public Object tryInvoke(MethodInvocation invocation, Object proxy) throws Throwable {
        ClassLoader originalClassLoader = overrideThreadContextClassLoader();
        try {
            return invocation.getMethod().invoke(proxy, invocation.getArguments());
        } catch (InvocationTargetException ex) {
            Throwable targetEx = ex.getTargetException();
            // Hessian 4.0 check: another layer of InvocationTargetException.
            if (targetEx instanceof InvocationTargetException) {
                targetEx = ((InvocationTargetException) targetEx).getTargetException();
            }
            if (targetEx instanceof HessianConnectionException) {
                throw convertHessianAccessException(targetEx);
            } else if (targetEx instanceof HessianException || targetEx instanceof HessianRuntimeException) {
                Throwable cause = targetEx.getCause();
                throw convertHessianAccessException(cause != null ? cause : targetEx);
            } else if (targetEx instanceof UndeclaredThrowableException) {
                UndeclaredThrowableException utex = (UndeclaredThrowableException) targetEx;
                throw convertHessianAccessException(utex.getUndeclaredThrowable());
            } else {
                throw targetEx;
            }
        } catch (Throwable ex) {
            throw new RemoteProxyFailureException("Failed to invoke Hessian proxy for remote service ["
                    + getServiceInterface() + "]", ex);
        } finally {
            resetThreadContextClassLoader(originalClassLoader);
        }
    }

    private RemoteAccessException convertHessianAccessException(Throwable ex) {
        if (ex instanceof HessianConnectionException || ex instanceof ConnectException) {
            return new RemoteConnectFailureException("Cannot connect to Hessian remote service at []", ex);
        } else {
            return new RemoteAccessException("Cannot access Hessian remote service at []", ex);
        }
    }

    @SuppressWarnings("rawtypes")
    public HessianClientRouter getClientRouter() {
        return clientRouter;
    }

    @SuppressWarnings("rawtypes")
    public void setClientRouter(HessianClientRouter clientRouter) {
        this.clientRouter = clientRouter;
    }

}
