View Javadoc
1   /**
2    * Copyright 2005-2014 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 java.io.InterruptedIOException;
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Proxy;
21  import java.net.ConnectException;
22  import java.net.NoRouteToHostException;
23  import java.net.SocketTimeoutException;
24  import java.net.UnknownHostException;
25  import java.util.ArrayList;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.commons.httpclient.ConnectTimeoutException;
31  import org.apache.commons.httpclient.ConnectionPoolTimeoutException;
32  import org.apache.commons.httpclient.NoHttpResponseException;
33  import org.apache.log4j.Logger;
34  import org.kuali.rice.core.api.util.ClassLoaderUtils;
35  import org.kuali.rice.core.api.util.ContextClassLoaderProxy;
36  import org.kuali.rice.core.api.util.reflect.BaseTargetedInvocationHandler;
37  import org.kuali.rice.ksb.api.KsbApiServiceLocator;
38  import org.kuali.rice.ksb.api.bus.Endpoint;
39  import org.kuali.rice.ksb.api.bus.ServiceConfiguration;
40  
41  
42  
43  public class BusClientFailureProxy extends BaseTargetedInvocationHandler<Object> {
44  
45  	private static final Logger LOG = Logger.getLogger(BusClientFailureProxy.class);
46  
47  	private final Object failoverLock = new Object();
48  	
49  	private ServiceConfiguration serviceConfiguration;
50  
51  	// exceptions that will cause this Proxy to remove the service from the bus
52  	private static List<Class<?>> serviceRemovalExceptions = new ArrayList<Class<?>>();
53  	private static List<Integer> serviceRemovalResponseCodes = new ArrayList<Integer>();
54  
55  	static {
56  		serviceRemovalExceptions.add(NoHttpResponseException.class);
57  		serviceRemovalExceptions.add(InterruptedIOException.class);
58  		serviceRemovalExceptions.add(UnknownHostException.class);
59  		serviceRemovalExceptions.add(NoRouteToHostException.class);
60  		serviceRemovalExceptions.add(ConnectTimeoutException.class);
61  		serviceRemovalExceptions.add(ConnectionPoolTimeoutException.class);
62  		serviceRemovalExceptions.add(ConnectException.class);
63          serviceRemovalExceptions.add(SocketTimeoutException.class);
64      }
65  	
66  	static {
67  	    serviceRemovalResponseCodes.add(new Integer(404));
68          serviceRemovalResponseCodes.add(new Integer(503));
69  	}
70  	
71  	private BusClientFailureProxy(Object target, ServiceConfiguration serviceConfiguration) {
72  		super(target);
73  		this.serviceConfiguration = serviceConfiguration;
74  	}
75  
76  	public static Object wrap(Object target, ServiceConfiguration serviceConfiguration) {
77  		return Proxy.newProxyInstance(ClassLoaderUtils.getDefaultClassLoader(), ContextClassLoaderProxy.getInterfacesToProxy(target), new BusClientFailureProxy(target, serviceConfiguration));
78  	}
79  
80  	protected Object invokeInternal(Object proxyObject, Method method, Object[] params) throws Throwable {
81  		Set<ServiceConfiguration> servicesTried = null;
82  		
83  		do {
84  			try {
85  				return method.invoke(getTarget(), params);
86  			} catch (Throwable throwable) {			
87  				if (isServiceRemovalException(throwable)) {
88  					synchronized (failoverLock) {
89                          LOG.error("Exception caught accessing remote service " + this.serviceConfiguration.getServiceName() + " at " + this.serviceConfiguration.getEndpointUrl(), throwable);
90                          if (servicesTried == null) {
91  							servicesTried = new HashSet<ServiceConfiguration>();
92  							servicesTried.add(serviceConfiguration);
93  						}
94  						Object failoverService = null;
95  						List<Endpoint> endpoints = KsbApiServiceLocator.getServiceBus().getEndpoints(serviceConfiguration.getServiceName(), serviceConfiguration.getApplicationId());
96  						for (Endpoint endpoint : endpoints) {
97  							if (!servicesTried.contains(endpoint.getServiceConfiguration())) {
98  								failoverService = endpoint.getService();
99                                  if(Proxy.isProxyClass(failoverService.getClass()) && Proxy.getInvocationHandler(failoverService) instanceof BusClientFailureProxy) {
100                                     failoverService = ((BusClientFailureProxy)Proxy.getInvocationHandler(failoverService)).getTarget();
101                                 }
102 								servicesTried.add(endpoint.getServiceConfiguration());
103                                 break; // KULRICE-8728: BusClientFailureProxy doesn't try all endpoint options
104 							}
105 						}									
106 						if (failoverService != null) {
107                             LOG.info("Refetched replacement service for service " + this.serviceConfiguration.getServiceName() + " at " + this.serviceConfiguration.getEndpointUrl());
108                             // as per KULRICE-4287, reassign target to the new service we just fetched, hopefully this one works better!
109 							setTarget(failoverService);
110 						} else {
111 							LOG.error("Didn't find replacement service throwing exception");
112 							throw throwable;					
113 						}
114 					}
115 				} else {
116 					throw throwable;
117 				}
118 			}
119 		} while (true);
120 	}
121 
122 	private static boolean isServiceRemovalException(Throwable throwable) {
123 		LOG.info("Checking for Service Removal Exception: " + throwable.getClass().getName());
124 		if (serviceRemovalExceptions.contains(throwable.getClass())) {
125 			LOG.info("Found a Service Removal Exception: " + throwable.getClass().getName());
126 			return true;
127 		} else if (throwable instanceof org.kuali.rice.ksb.messaging.HttpException) {
128 			org.kuali.rice.ksb.messaging.HttpException httpException = (org.kuali.rice.ksb.messaging.HttpException)throwable;
129 			if (serviceRemovalResponseCodes.contains(httpException.getResponseCode())) {
130 				LOG.info("Found a Service Removal Exception because of a " + httpException.getResponseCode() + " " + throwable.getClass().getName());
131 				return true;
132 			}
133 		} else if (throwable instanceof org.apache.cxf.transport.http.HTTPException) {
134 			org.apache.cxf.transport.http.HTTPException httpException = (org.apache.cxf.transport.http.HTTPException)throwable;
135 			if (serviceRemovalResponseCodes.contains(httpException.getResponseCode())) {
136 				LOG.info("Found a Service Removal Exception because of a " + httpException.getResponseCode() + " " + throwable.getClass().getName());
137 				return true;
138 			}
139 		}
140 		if (throwable.getCause() != null) {
141 			LOG.info("Unwrapping Throwable cause to check for service removal exception from: " + throwable.getClass().getName());
142 			return isServiceRemovalException(throwable.getCause());
143 		}
144 		return false;
145 	}
146 
147 }