View Javadoc

1   /*
2    * Copyright 2006-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.ksb.messaging;
17  
18  import org.apache.commons.httpclient.ConnectTimeoutException;
19  import org.apache.commons.httpclient.ConnectionPoolTimeoutException;
20  import org.apache.commons.httpclient.NoHttpResponseException;
21  import org.apache.log4j.Logger;
22  import org.kuali.rice.core.api.util.ClassLoaderUtils;
23  import org.kuali.rice.core.api.util.reflect.BaseTargetedInvocationHandler;
24  import org.kuali.rice.core.impl.resourceloader.ContextClassLoaderProxy;
25  import org.kuali.rice.ksb.api.KsbApiServiceLocator;
26  import org.kuali.rice.ksb.api.bus.Endpoint;
27  import org.kuali.rice.ksb.api.bus.ServiceConfiguration;
28  
29  import java.io.InterruptedIOException;
30  import java.lang.reflect.Method;
31  import java.lang.reflect.Proxy;
32  import java.net.ConnectException;
33  import java.net.NoRouteToHostException;
34  import java.net.UnknownHostException;
35  import java.util.ArrayList;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Set;
39  
40  
41  
42  public class BusClientFailureProxy extends BaseTargetedInvocationHandler {
43  
44  	private static final Logger LOG = Logger.getLogger(BusClientFailureProxy.class);
45  
46  	private final Object failoverLock = new Object();
47  	
48  	private ServiceConfiguration serviceConfiguration;
49  
50  	// exceptions that will cause this Proxy to remove the service from the bus
51  	private static List<Class<?>> serviceRemovalExceptions = new ArrayList<Class<?>>();
52  	private static List<Integer> serviceRemovalResponseCodes = new ArrayList<Integer>();
53  
54  	static {
55  		serviceRemovalExceptions.add(NoHttpResponseException.class);
56  		serviceRemovalExceptions.add(InterruptedIOException.class);
57  		serviceRemovalExceptions.add(UnknownHostException.class);
58  		serviceRemovalExceptions.add(NoRouteToHostException.class);
59  		serviceRemovalExceptions.add(ConnectTimeoutException.class);
60  		serviceRemovalExceptions.add(ConnectionPoolTimeoutException.class);
61  		serviceRemovalExceptions.add(ConnectException.class);
62  	}
63  	
64  	static {
65  	    serviceRemovalResponseCodes.add(new Integer(404));
66          serviceRemovalResponseCodes.add(new Integer(503));
67  	}
68  	
69  	private BusClientFailureProxy(Object target, ServiceConfiguration serviceConfiguration) {
70  		super(target);
71  		this.serviceConfiguration = serviceConfiguration;
72  	}
73  
74  	public static Object wrap(Object target, ServiceConfiguration serviceConfiguration) {
75  		return Proxy.newProxyInstance(ClassLoaderUtils.getDefaultClassLoader(), ContextClassLoaderProxy.getInterfacesToProxy(target), new BusClientFailureProxy(target, serviceConfiguration));
76  	}
77  
78  	protected Object invokeInternal(Object proxyObject, Method method, Object[] params) throws Throwable {
79  		Set<ServiceConfiguration> servicesTried = null;
80  		
81  		do {
82  			try {
83  				return method.invoke(getTarget(), params);
84  			} catch (Throwable throwable) {			
85  				if (isServiceRemovalException(throwable)) {
86  					synchronized (failoverLock) {
87  						LOG.error("Exception caught accessing remote service " + this.serviceConfiguration.getServiceName(), throwable);
88  						if (servicesTried == null) {
89  							servicesTried = new HashSet<ServiceConfiguration>();
90  							servicesTried.add(serviceConfiguration);
91  						}
92  						Object failoverService = null;
93  						List<Endpoint> endpoints = KsbApiServiceLocator.getServiceBus().getEndpoints(serviceConfiguration.getServiceName());
94  						for (Endpoint endpoint : endpoints) {
95  							if (!servicesTried.contains(endpoint.getServiceConfiguration())) {
96  								failoverService = endpoint.getService();
97  								servicesTried.add(endpoint.getServiceConfiguration());
98  							}
99  						}									
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 }