001 /** 002 * Copyright 2005-2014 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 java.io.InterruptedIOException; 019 import java.lang.reflect.Method; 020 import java.lang.reflect.Proxy; 021 import java.net.ConnectException; 022 import java.net.NoRouteToHostException; 023 import java.net.SocketTimeoutException; 024 import java.net.UnknownHostException; 025 import java.util.ArrayList; 026 import java.util.HashSet; 027 import java.util.List; 028 import java.util.Set; 029 030 import org.apache.commons.httpclient.ConnectTimeoutException; 031 import org.apache.commons.httpclient.ConnectionPoolTimeoutException; 032 import org.apache.commons.httpclient.NoHttpResponseException; 033 import org.apache.log4j.Logger; 034 import org.kuali.rice.core.api.util.ClassLoaderUtils; 035 import org.kuali.rice.core.api.util.ContextClassLoaderProxy; 036 import org.kuali.rice.core.api.util.reflect.BaseTargetedInvocationHandler; 037 import org.kuali.rice.ksb.api.KsbApiServiceLocator; 038 import org.kuali.rice.ksb.api.bus.Endpoint; 039 import org.kuali.rice.ksb.api.bus.ServiceConfiguration; 040 041 042 043 public class BusClientFailureProxy extends BaseTargetedInvocationHandler { 044 045 private static final Logger LOG = Logger.getLogger(BusClientFailureProxy.class); 046 047 private final Object failoverLock = new Object(); 048 049 private ServiceConfiguration serviceConfiguration; 050 051 // exceptions that will cause this Proxy to remove the service from the bus 052 private static List<Class<?>> serviceRemovalExceptions = new ArrayList<Class<?>>(); 053 private static List<Integer> serviceRemovalResponseCodes = new ArrayList<Integer>(); 054 055 static { 056 serviceRemovalExceptions.add(NoHttpResponseException.class); 057 serviceRemovalExceptions.add(InterruptedIOException.class); 058 serviceRemovalExceptions.add(UnknownHostException.class); 059 serviceRemovalExceptions.add(NoRouteToHostException.class); 060 serviceRemovalExceptions.add(ConnectTimeoutException.class); 061 serviceRemovalExceptions.add(ConnectionPoolTimeoutException.class); 062 serviceRemovalExceptions.add(ConnectException.class); 063 serviceRemovalExceptions.add(SocketTimeoutException.class); 064 } 065 066 static { 067 serviceRemovalResponseCodes.add(new Integer(404)); 068 serviceRemovalResponseCodes.add(new Integer(503)); 069 } 070 071 private BusClientFailureProxy(Object target, ServiceConfiguration serviceConfiguration) { 072 super(target); 073 this.serviceConfiguration = serviceConfiguration; 074 } 075 076 public static Object wrap(Object target, ServiceConfiguration serviceConfiguration) { 077 return Proxy.newProxyInstance(ClassLoaderUtils.getDefaultClassLoader(), ContextClassLoaderProxy.getInterfacesToProxy(target), new BusClientFailureProxy(target, serviceConfiguration)); 078 } 079 080 protected Object invokeInternal(Object proxyObject, Method method, Object[] params) throws Throwable { 081 Set<ServiceConfiguration> servicesTried = null; 082 083 do { 084 try { 085 return method.invoke(getTarget(), params); 086 } catch (Throwable throwable) { 087 if (isServiceRemovalException(throwable)) { 088 synchronized (failoverLock) { 089 LOG.error("Exception caught accessing remote service " + this.serviceConfiguration.getServiceName() + " at " + this.serviceConfiguration.getEndpointUrl(), throwable); 090 if (servicesTried == null) { 091 servicesTried = new HashSet<ServiceConfiguration>(); 092 servicesTried.add(serviceConfiguration); 093 } 094 Object failoverService = null; 095 List<Endpoint> endpoints = KsbApiServiceLocator.getServiceBus().getEndpoints(serviceConfiguration.getServiceName(), serviceConfiguration.getApplicationId()); 096 for (Endpoint endpoint : endpoints) { 097 if (!servicesTried.contains(endpoint.getServiceConfiguration())) { 098 failoverService = endpoint.getService(); 099 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 }