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 }