001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.ksb.messaging;
017    
018    import org.apache.commons.httpclient.ConnectTimeoutException;
019    import org.apache.commons.httpclient.ConnectionPoolTimeoutException;
020    import org.apache.commons.httpclient.NoHttpResponseException;
021    import org.apache.log4j.Logger;
022    import org.kuali.rice.core.api.util.ClassLoaderUtils;
023    import org.kuali.rice.core.api.util.ContextClassLoaderProxy;
024    import org.kuali.rice.core.api.util.reflect.BaseTargetedInvocationHandler;
025    import org.kuali.rice.ksb.api.KsbApiServiceLocator;
026    import org.kuali.rice.ksb.api.bus.Endpoint;
027    import org.kuali.rice.ksb.api.bus.ServiceConfiguration;
028    
029    import java.io.InterruptedIOException;
030    import java.lang.reflect.Method;
031    import java.lang.reflect.Proxy;
032    import java.net.ConnectException;
033    import java.net.NoRouteToHostException;
034    import java.net.UnknownHostException;
035    import java.util.ArrayList;
036    import java.util.HashSet;
037    import java.util.List;
038    import java.util.Set;
039    
040    
041    
042    public class BusClientFailureProxy extends BaseTargetedInvocationHandler {
043    
044            private static final Logger LOG = Logger.getLogger(BusClientFailureProxy.class);
045    
046            private final Object failoverLock = new Object();
047            
048            private ServiceConfiguration serviceConfiguration;
049    
050            // exceptions that will cause this Proxy to remove the service from the bus
051            private static List<Class<?>> serviceRemovalExceptions = new ArrayList<Class<?>>();
052            private static List<Integer> serviceRemovalResponseCodes = new ArrayList<Integer>();
053    
054            static {
055                    serviceRemovalExceptions.add(NoHttpResponseException.class);
056                    serviceRemovalExceptions.add(InterruptedIOException.class);
057                    serviceRemovalExceptions.add(UnknownHostException.class);
058                    serviceRemovalExceptions.add(NoRouteToHostException.class);
059                    serviceRemovalExceptions.add(ConnectTimeoutException.class);
060                    serviceRemovalExceptions.add(ConnectionPoolTimeoutException.class);
061                    serviceRemovalExceptions.add(ConnectException.class);
062            }
063            
064            static {
065                serviceRemovalResponseCodes.add(new Integer(404));
066            serviceRemovalResponseCodes.add(new Integer(503));
067            }
068            
069            private BusClientFailureProxy(Object target, ServiceConfiguration serviceConfiguration) {
070                    super(target);
071                    this.serviceConfiguration = serviceConfiguration;
072            }
073    
074            public static Object wrap(Object target, ServiceConfiguration serviceConfiguration) {
075                    return Proxy.newProxyInstance(ClassLoaderUtils.getDefaultClassLoader(), ContextClassLoaderProxy.getInterfacesToProxy(target), new BusClientFailureProxy(target, serviceConfiguration));
076            }
077    
078            protected Object invokeInternal(Object proxyObject, Method method, Object[] params) throws Throwable {
079                    Set<ServiceConfiguration> servicesTried = null;
080                    
081                    do {
082                            try {
083                                    return method.invoke(getTarget(), params);
084                            } catch (Throwable throwable) {                 
085                                    if (isServiceRemovalException(throwable)) {
086                                            synchronized (failoverLock) {
087                                                    LOG.error("Exception caught accessing remote service " + this.serviceConfiguration.getServiceName(), throwable);
088                                                    if (servicesTried == null) {
089                                                            servicesTried = new HashSet<ServiceConfiguration>();
090                                                            servicesTried.add(serviceConfiguration);
091                                                    }
092                                                    Object failoverService = null;
093                                                    List<Endpoint> endpoints = KsbApiServiceLocator.getServiceBus().getEndpoints(serviceConfiguration.getServiceName());
094                                                    for (Endpoint endpoint : endpoints) {
095                                                            if (!servicesTried.contains(endpoint.getServiceConfiguration())) {
096                                                                    failoverService = endpoint.getService();
097                                                                    servicesTried.add(endpoint.getServiceConfiguration());
098                                                            }
099                                                    }                                                                       
100                                                    if (failoverService != null) {
101                                                            LOG.info("Refetched replacement service for service " + this.serviceConfiguration.getServiceName());
102                                                            // as per KULRICE-4287, reassign target to the new service we just fetched, hopefully this one works better!
103                                                            setTarget(failoverService);
104                                                    } else {
105                                                            LOG.error("Didn't find replacement service throwing exception");
106                                                            throw throwable;                                        
107                                                    }
108                                            }
109                                    } else {
110                                            throw throwable;
111                                    }
112                            }
113                    } while (true);
114            }
115    
116            private static boolean isServiceRemovalException(Throwable throwable) {
117                    LOG.info("Checking for Service Removal Exception: " + throwable.getClass().getName());
118                    if (serviceRemovalExceptions.contains(throwable.getClass())) {
119                            LOG.info("Found a Service Removal Exception: " + throwable.getClass().getName());
120                            return true;
121                    } else if (throwable instanceof HttpException) {
122                            HttpException httpException = (HttpException)throwable;
123                            if (serviceRemovalResponseCodes.contains(httpException.getResponseCode())) {
124                                    LOG.info("Found a Service Removal Exception because of a " + httpException.getResponseCode() + " " + throwable.getClass().getName());
125                                    return true;
126                            }
127                    }
128                    if (throwable.getCause() != null) {
129                            LOG.info("Unwrapping Throwable cause to check for service removal exception from: " + throwable.getClass().getName());
130                            return isServiceRemovalException(throwable.getCause());
131                    }
132                    return false;
133            }
134    
135    }