001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache 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 * https://www.apache.org/licenses/LICENSE-2.0 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 017package org.springframework.ejb.access; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.rmi.RemoteException; 022 023import javax.ejb.EJBHome; 024import javax.ejb.EJBObject; 025import javax.naming.NamingException; 026 027import org.aopalliance.intercept.MethodInvocation; 028 029import org.springframework.lang.Nullable; 030import org.springframework.remoting.RemoteConnectFailureException; 031import org.springframework.remoting.RemoteLookupFailureException; 032import org.springframework.remoting.rmi.RmiClientInterceptorUtils; 033 034/** 035 * Base class for interceptors proxying remote Stateless Session Beans. 036 * Designed for EJB 2.x, but works for EJB 3 Session Beans as well. 037 * 038 * <p>Such an interceptor must be the last interceptor in the advice chain. 039 * In this case, there is no target object. 040 * 041 * @author Rod Johnson 042 * @author Juergen Hoeller 043 */ 044public abstract class AbstractRemoteSlsbInvokerInterceptor extends AbstractSlsbInvokerInterceptor { 045 046 private boolean refreshHomeOnConnectFailure = false; 047 048 private volatile boolean homeAsComponent = false; 049 050 051 052 /** 053 * Set whether to refresh the EJB home on connect failure. 054 * Default is "false". 055 * <p>Can be turned on to allow for hot restart of the EJB server. 056 * If a cached EJB home throws an RMI exception that indicates a 057 * remote connect failure, a fresh home will be fetched and the 058 * invocation will be retried. 059 * @see java.rmi.ConnectException 060 * @see java.rmi.ConnectIOException 061 * @see java.rmi.NoSuchObjectException 062 */ 063 public void setRefreshHomeOnConnectFailure(boolean refreshHomeOnConnectFailure) { 064 this.refreshHomeOnConnectFailure = refreshHomeOnConnectFailure; 065 } 066 067 @Override 068 protected boolean isHomeRefreshable() { 069 return this.refreshHomeOnConnectFailure; 070 } 071 072 073 /** 074 * Check for EJB3-style home object that serves as EJB component directly. 075 */ 076 @Override 077 protected Method getCreateMethod(Object home) throws EjbAccessException { 078 if (this.homeAsComponent) { 079 return null; 080 } 081 if (!(home instanceof EJBHome)) { 082 // An EJB3 Session Bean... 083 this.homeAsComponent = true; 084 return null; 085 } 086 return super.getCreateMethod(home); 087 } 088 089 090 /** 091 * Fetches an EJB home object and delegates to {@code doInvoke}. 092 * <p>If configured to refresh on connect failure, it will call 093 * {@link #refreshAndRetry} on corresponding RMI exceptions. 094 * @see #getHome 095 * @see #doInvoke 096 * @see #refreshAndRetry 097 */ 098 @Override 099 @Nullable 100 public Object invokeInContext(MethodInvocation invocation) throws Throwable { 101 try { 102 return doInvoke(invocation); 103 } 104 catch (RemoteConnectFailureException ex) { 105 return handleRemoteConnectFailure(invocation, ex); 106 } 107 catch (RemoteException ex) { 108 if (isConnectFailure(ex)) { 109 return handleRemoteConnectFailure(invocation, ex); 110 } 111 else { 112 throw ex; 113 } 114 } 115 } 116 117 /** 118 * Determine whether the given RMI exception indicates a connect failure. 119 * <p>The default implementation delegates to RmiClientInterceptorUtils. 120 * @param ex the RMI exception to check 121 * @return whether the exception should be treated as connect failure 122 * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure 123 */ 124 protected boolean isConnectFailure(RemoteException ex) { 125 return RmiClientInterceptorUtils.isConnectFailure(ex); 126 } 127 128 @Nullable 129 private Object handleRemoteConnectFailure(MethodInvocation invocation, Exception ex) throws Throwable { 130 if (this.refreshHomeOnConnectFailure) { 131 if (logger.isDebugEnabled()) { 132 logger.debug("Could not connect to remote EJB [" + getJndiName() + "] - retrying", ex); 133 } 134 else if (logger.isWarnEnabled()) { 135 logger.warn("Could not connect to remote EJB [" + getJndiName() + "] - retrying"); 136 } 137 return refreshAndRetry(invocation); 138 } 139 else { 140 throw ex; 141 } 142 } 143 144 /** 145 * Refresh the EJB home object and retry the given invocation. 146 * Called by invoke on connect failure. 147 * @param invocation the AOP method invocation 148 * @return the invocation result, if any 149 * @throws Throwable in case of invocation failure 150 * @see #invoke 151 */ 152 @Nullable 153 protected Object refreshAndRetry(MethodInvocation invocation) throws Throwable { 154 try { 155 refreshHome(); 156 } 157 catch (NamingException ex) { 158 throw new RemoteLookupFailureException("Failed to locate remote EJB [" + getJndiName() + "]", ex); 159 } 160 return doInvoke(invocation); 161 } 162 163 164 /** 165 * Perform the given invocation on the current EJB home. 166 * Template method to be implemented by subclasses. 167 * @param invocation the AOP method invocation 168 * @return the invocation result, if any 169 * @throws Throwable in case of invocation failure 170 * @see #getHome 171 * @see #newSessionBeanInstance 172 */ 173 @Nullable 174 protected abstract Object doInvoke(MethodInvocation invocation) throws Throwable; 175 176 177 /** 178 * Return a new instance of the stateless session bean. 179 * To be invoked by concrete remote SLSB invoker subclasses. 180 * <p>Can be overridden to change the algorithm. 181 * @throws NamingException if thrown by JNDI 182 * @throws InvocationTargetException if thrown by the create method 183 * @see #create 184 */ 185 protected Object newSessionBeanInstance() throws NamingException, InvocationTargetException { 186 if (logger.isDebugEnabled()) { 187 logger.debug("Trying to create reference to remote EJB"); 188 } 189 Object ejbInstance = create(); 190 if (logger.isDebugEnabled()) { 191 logger.debug("Obtained reference to remote EJB: " + ejbInstance); 192 } 193 return ejbInstance; 194 } 195 196 /** 197 * Remove the given EJB instance. 198 * To be invoked by concrete remote SLSB invoker subclasses. 199 * @param ejb the EJB instance to remove 200 * @see javax.ejb.EJBObject#remove 201 */ 202 protected void removeSessionBeanInstance(@Nullable EJBObject ejb) { 203 if (ejb != null && !this.homeAsComponent) { 204 try { 205 ejb.remove(); 206 } 207 catch (Throwable ex) { 208 logger.warn("Could not invoke 'remove' on remote EJB proxy", ex); 209 } 210 } 211 } 212 213}