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 }