001/** 002 * Copyright 2005-2015 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 */ 016package org.kuali.rice.ksb.messaging; 017 018import org.apache.log4j.Logger; 019import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 020import org.kuali.rice.core.api.util.ClassLoaderUtils; 021import org.kuali.rice.core.api.util.ContextClassLoaderProxy; 022import org.kuali.rice.core.api.util.reflect.BaseTargetedInvocationHandler; 023import org.kuali.rice.ksb.api.KsbApiServiceLocator; 024import org.kuali.rice.ksb.api.bus.Endpoint; 025import org.kuali.rice.ksb.api.bus.ServiceConfiguration; 026 027import java.lang.reflect.Method; 028import java.lang.reflect.Proxy; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032 033 034 035public class BusClientFailureProxy extends BaseTargetedInvocationHandler<Object> { 036 037 private static final Logger LOG = Logger.getLogger(BusClientFailureProxy.class); 038 039 static final String SERVICE_REMOVAL_EXCEPTIONS_BEAN = "rice.ksb.serviceRemovalExceptions"; 040 static final String SERVICE_REMOVAL_RESPONSE_CODES_BEAN = "rice.ksb.serviceRemovalResponseCodes"; 041 042 private final Object failoverLock = new Object(); 043 044 private ServiceConfiguration serviceConfiguration; 045 046 private BusClientFailureProxy(Object target, ServiceConfiguration serviceConfiguration) { 047 super(target); 048 this.serviceConfiguration = serviceConfiguration; 049 } 050 051 public static Object wrap(Object target, ServiceConfiguration serviceConfiguration) { 052 return Proxy.newProxyInstance(ClassLoaderUtils.getDefaultClassLoader(), ContextClassLoaderProxy.getInterfacesToProxy(target), new BusClientFailureProxy(target, serviceConfiguration)); 053 } 054 055 protected Object invokeInternal(Object proxyObject, Method method, Object[] params) throws Throwable { 056 Set<ServiceConfiguration> servicesTried = null; 057 058 do { 059 try { 060 return method.invoke(getTarget(), params); 061 } catch (Throwable throwable) { 062 if (isServiceRemovalException(throwable)) { 063 synchronized (failoverLock) { 064 LOG.error("Exception caught accessing remote service " + this.serviceConfiguration.getServiceName() + " at " + this.serviceConfiguration.getEndpointUrl(), throwable); 065 if (servicesTried == null) { 066 servicesTried = new HashSet<ServiceConfiguration>(); 067 servicesTried.add(serviceConfiguration); 068 } 069 Object failoverService = null; 070 List<Endpoint> endpoints = KsbApiServiceLocator.getServiceBus().getEndpoints(serviceConfiguration.getServiceName(), serviceConfiguration.getApplicationId()); 071 for (Endpoint endpoint : endpoints) { 072 if (!servicesTried.contains(endpoint.getServiceConfiguration())) { 073 failoverService = endpoint.getService(); 074 if(Proxy.isProxyClass(failoverService.getClass()) && Proxy.getInvocationHandler(failoverService) instanceof BusClientFailureProxy) { 075 failoverService = ((BusClientFailureProxy)Proxy.getInvocationHandler(failoverService)).getTarget(); 076 } 077 servicesTried.add(endpoint.getServiceConfiguration()); 078 break; // KULRICE-8728: BusClientFailureProxy doesn't try all endpoint options 079 } 080 } 081 if (failoverService != null) { 082 LOG.info("Refetched replacement service for service " + this.serviceConfiguration.getServiceName() + " at " + this.serviceConfiguration.getEndpointUrl()); 083 // as per KULRICE-4287, reassign target to the new service we just fetched, hopefully this one works better! 084 setTarget(failoverService); 085 } else { 086 LOG.error("Didn't find replacement service throwing exception"); 087 throw throwable; 088 } 089 } 090 } else { 091 throw throwable; 092 } 093 } 094 } while (true); 095 } 096 097 private static boolean isServiceRemovalException(Throwable throwable) { 098 LOG.info("Checking for Service Removal Exception: " + throwable.getClass().getName()); 099 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}