View Javadoc
1   /**
2    * Copyright 2005-2015 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.log4j.Logger;
19  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
20  import org.kuali.rice.core.api.util.ClassLoaderUtils;
21  import org.kuali.rice.core.api.util.ContextClassLoaderProxy;
22  import org.kuali.rice.core.api.util.reflect.BaseTargetedInvocationHandler;
23  import org.kuali.rice.ksb.api.KsbApiServiceLocator;
24  import org.kuali.rice.ksb.api.bus.Endpoint;
25  import org.kuali.rice.ksb.api.bus.ServiceConfiguration;
26  
27  import java.lang.reflect.Method;
28  import java.lang.reflect.Proxy;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Set;
32  
33  
34  
35  public class BusClientFailureProxy extends BaseTargetedInvocationHandler<Object> {
36  
37  	private static final Logger LOG = Logger.getLogger(BusClientFailureProxy.class);
38  
39      static final String SERVICE_REMOVAL_EXCEPTIONS_BEAN = "rice.ksb.serviceRemovalExceptions";
40      static final String SERVICE_REMOVAL_RESPONSE_CODES_BEAN = "rice.ksb.serviceRemovalResponseCodes";
41  
42  	private final Object failoverLock = new Object();
43  	
44  	private ServiceConfiguration serviceConfiguration;
45  
46  	private BusClientFailureProxy(Object target, ServiceConfiguration serviceConfiguration) {
47  		super(target);
48  		this.serviceConfiguration = serviceConfiguration;
49  	}
50  
51  	public static Object wrap(Object target, ServiceConfiguration serviceConfiguration) {
52  		return Proxy.newProxyInstance(ClassLoaderUtils.getDefaultClassLoader(), ContextClassLoaderProxy.getInterfacesToProxy(target), new BusClientFailureProxy(target, serviceConfiguration));
53  	}
54  
55  	protected Object invokeInternal(Object proxyObject, Method method, Object[] params) throws Throwable {
56  		Set<ServiceConfiguration> servicesTried = null;
57  		
58  		do {
59  			try {
60  				return method.invoke(getTarget(), params);
61  			} catch (Throwable throwable) {			
62  				if (isServiceRemovalException(throwable)) {
63  					synchronized (failoverLock) {
64                          LOG.error("Exception caught accessing remote service " + this.serviceConfiguration.getServiceName() + " at " + this.serviceConfiguration.getEndpointUrl(), throwable);
65                          if (servicesTried == null) {
66  							servicesTried = new HashSet<ServiceConfiguration>();
67  							servicesTried.add(serviceConfiguration);
68  						}
69  						Object failoverService = null;
70  						List<Endpoint> endpoints = KsbApiServiceLocator.getServiceBus().getEndpoints(serviceConfiguration.getServiceName(), serviceConfiguration.getApplicationId());
71  						for (Endpoint endpoint : endpoints) {
72  							if (!servicesTried.contains(endpoint.getServiceConfiguration())) {
73  								failoverService = endpoint.getService();
74                                  if(Proxy.isProxyClass(failoverService.getClass()) && Proxy.getInvocationHandler(failoverService) instanceof BusClientFailureProxy) {
75                                      failoverService = ((BusClientFailureProxy)Proxy.getInvocationHandler(failoverService)).getTarget();
76                                  }
77  								servicesTried.add(endpoint.getServiceConfiguration());
78                                  break; // KULRICE-8728: BusClientFailureProxy doesn't try all endpoint options
79  							}
80  						}									
81  						if (failoverService != null) {
82                              LOG.info("Refetched replacement service for service " + this.serviceConfiguration.getServiceName() + " at " + this.serviceConfiguration.getEndpointUrl());
83                              // as per KULRICE-4287, reassign target to the new service we just fetched, hopefully this one works better!
84  							setTarget(failoverService);
85  						} else {
86  							LOG.error("Didn't find replacement service throwing exception");
87  							throw throwable;					
88  						}
89  					}
90  				} else {
91  					throw throwable;
92  				}
93  			}
94  		} while (true);
95  	}
96  
97  	private static boolean isServiceRemovalException(Throwable throwable) {
98  		LOG.info("Checking for Service Removal Exception: " + throwable.getClass().getName());
99  		if (getServiceRemovalExceptions().contains(throwable.getClass())) {
100 			LOG.info("Found a Service Removal Exception: " + throwable.getClass().getName());
101 			return true;
102 		} else if (throwable instanceof org.kuali.rice.ksb.messaging.HttpException) {
103 			org.kuali.rice.ksb.messaging.HttpException httpException = (org.kuali.rice.ksb.messaging.HttpException)throwable;
104 			if (getServiceRemovalResponseCodes().contains(httpException.getResponseCode())) {
105 				LOG.info("Found a Service Removal Exception because of a " + httpException.getResponseCode() + " " + throwable.getClass().getName());
106 				return true;
107 			}
108 		} else if (throwable instanceof org.apache.cxf.transport.http.HTTPException) {
109 			org.apache.cxf.transport.http.HTTPException httpException = (org.apache.cxf.transport.http.HTTPException)throwable;
110 			if (getServiceRemovalResponseCodes().contains(httpException.getResponseCode())) {
111 				LOG.info("Found a Service Removal Exception because of a " + httpException.getResponseCode() + " " + throwable.getClass().getName());
112 				return true;
113 			}
114 		}
115 		if (throwable.getCause() != null) {
116 			LOG.info("Unwrapping Throwable cause to check for service removal exception from: " + throwable.getClass().getName());
117 			return isServiceRemovalException(throwable.getCause());
118 		}
119 		return false;
120 	}
121 
122     /**
123      * Lazy initialization holder class idiom for static fields, see Effective Java item 71
124      */
125     private static class ServiceRemovalExceptionsHolder {
126         static final List<Class<?>> serviceRemovalExceptions =
127                 GlobalResourceLoader.getService(SERVICE_REMOVAL_EXCEPTIONS_BEAN);
128     }
129 
130     /**
131      * Get the list of exception classes that are considered service removal exceptions.
132      *
133      * <p>These are the exceptions that, when thrown from a service proxy, indicate that we should fail over to the
134      * next available endpoint for that service and remove the failing endpoint from the bus.</p>
135      *
136      * <p>On first call, the bean specified by {@link #SERVICE_REMOVAL_EXCEPTIONS_BEAN} will be lazily assigned and
137      * used.</p>
138      */
139     private static List<Class<?>> getServiceRemovalExceptions() {
140         return ServiceRemovalExceptionsHolder.serviceRemovalExceptions;
141     }
142 
143     /**
144      * Lazy initialization holder class idiom for static fields, see Effective Java item 71
145      */
146     private static class ServiceRemovalResponseCodesHolder {
147         static final List<Integer> serviceRemovalResponseCodes =
148                 GlobalResourceLoader.getService(SERVICE_REMOVAL_RESPONSE_CODES_BEAN);
149     }
150 
151     /**
152      * Get the list of HTTP response codes that indicate the need to fail over.
153      *
154      * <p>These are the response codes that, when detected within an exception thrown by a service proxy, indicate
155      * that we should fail over to the next available endpoint for that service and remove the failing endpoint from
156      * the bus.</p>
157      *
158      * <p>On first call, the bean specified by {@link #SERVICE_REMOVAL_RESPONSE_CODES_BEAN} will be lazily assigned and
159      * used.</p>
160      */
161     private static List<Integer> getServiceRemovalResponseCodes() {
162         return ServiceRemovalResponseCodesHolder.serviceRemovalResponseCodes;
163     }
164 
165 }