001/* 002 * Copyright 2002-2012 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; 022import javax.ejb.EJBHome; 023import javax.ejb.EJBObject; 024import javax.naming.NamingException; 025import javax.rmi.PortableRemoteObject; 026 027import org.aopalliance.intercept.MethodInvocation; 028 029import org.springframework.remoting.RemoteConnectFailureException; 030import org.springframework.remoting.RemoteLookupFailureException; 031import org.springframework.remoting.rmi.RmiClientInterceptorUtils; 032 033/** 034 * Base class for interceptors proxying remote Stateless Session Beans. 035 * Designed for EJB 2.x, but works for EJB 3 Session Beans as well. 036 * 037 * <p>Such an interceptor must be the last interceptor in the advice chain. 038 * In this case, there is no target object. 039 * 040 * @author Rod Johnson 041 * @author Juergen Hoeller 042 */ 043public abstract class AbstractRemoteSlsbInvokerInterceptor extends AbstractSlsbInvokerInterceptor { 044 045 private Class<?> homeInterface; 046 047 private boolean refreshHomeOnConnectFailure = false; 048 049 private volatile boolean homeAsComponent = false; 050 051 052 053 /** 054 * Set a home interface that this invoker will narrow to before performing 055 * the parameterless SLSB {@code create()} call that returns the actual 056 * SLSB proxy. 057 * <p>Default is none, which will work on all J2EE servers that are not based 058 * on CORBA. A plain {@code javax.ejb.EJBHome} interface is known to be 059 * sufficient to make a WebSphere 5.0 Remote SLSB work. On other servers, 060 * the specific home interface for the target SLSB might be necessary. 061 */ 062 public void setHomeInterface(Class<?> homeInterface) { 063 if (homeInterface != null && !homeInterface.isInterface()) { 064 throw new IllegalArgumentException( 065 "Home interface class [" + homeInterface.getClass() + "] is not an interface"); 066 } 067 this.homeInterface = homeInterface; 068 } 069 070 /** 071 * Set whether to refresh the EJB home on connect failure. 072 * Default is "false". 073 * <p>Can be turned on to allow for hot restart of the EJB server. 074 * If a cached EJB home throws an RMI exception that indicates a 075 * remote connect failure, a fresh home will be fetched and the 076 * invocation will be retried. 077 * @see java.rmi.ConnectException 078 * @see java.rmi.ConnectIOException 079 * @see java.rmi.NoSuchObjectException 080 */ 081 public void setRefreshHomeOnConnectFailure(boolean refreshHomeOnConnectFailure) { 082 this.refreshHomeOnConnectFailure = refreshHomeOnConnectFailure; 083 } 084 085 @Override 086 protected boolean isHomeRefreshable() { 087 return this.refreshHomeOnConnectFailure; 088 } 089 090 091 /** 092 * This overridden lookup implementation performs a narrow operation 093 * after the JNDI lookup, provided that a home interface is specified. 094 * @see #setHomeInterface 095 * @see javax.rmi.PortableRemoteObject#narrow 096 */ 097 @Override 098 protected Object lookup() throws NamingException { 099 Object homeObject = super.lookup(); 100 if (this.homeInterface != null) { 101 try { 102 homeObject = PortableRemoteObject.narrow(homeObject, this.homeInterface); 103 } 104 catch (ClassCastException ex) { 105 throw new RemoteLookupFailureException( 106 "Could not narrow EJB home stub to home interface [" + this.homeInterface.getName() + "]", ex); 107 } 108 } 109 return homeObject; 110 } 111 112 /** 113 * Check for EJB3-style home object that serves as EJB component directly. 114 */ 115 @Override 116 protected Method getCreateMethod(Object home) throws EjbAccessException { 117 if (this.homeAsComponent) { 118 return null; 119 } 120 if (!(home instanceof EJBHome)) { 121 // An EJB3 Session Bean... 122 this.homeAsComponent = true; 123 return null; 124 } 125 return super.getCreateMethod(home); 126 } 127 128 129 /** 130 * Fetches an EJB home object and delegates to {@code doInvoke}. 131 * <p>If configured to refresh on connect failure, it will call 132 * {@link #refreshAndRetry} on corresponding RMI exceptions. 133 * @see #getHome 134 * @see #doInvoke 135 * @see #refreshAndRetry 136 */ 137 @Override 138 public Object invokeInContext(MethodInvocation invocation) throws Throwable { 139 try { 140 return doInvoke(invocation); 141 } 142 catch (RemoteConnectFailureException ex) { 143 return handleRemoteConnectFailure(invocation, ex); 144 } 145 catch (RemoteException ex) { 146 if (isConnectFailure(ex)) { 147 return handleRemoteConnectFailure(invocation, ex); 148 } 149 else { 150 throw ex; 151 } 152 } 153 } 154 155 /** 156 * Determine whether the given RMI exception indicates a connect failure. 157 * <p>The default implementation delegates to RmiClientInterceptorUtils. 158 * @param ex the RMI exception to check 159 * @return whether the exception should be treated as connect failure 160 * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure 161 */ 162 protected boolean isConnectFailure(RemoteException ex) { 163 return RmiClientInterceptorUtils.isConnectFailure(ex); 164 } 165 166 private Object handleRemoteConnectFailure(MethodInvocation invocation, Exception ex) throws Throwable { 167 if (this.refreshHomeOnConnectFailure) { 168 if (logger.isDebugEnabled()) { 169 logger.debug("Could not connect to remote EJB [" + getJndiName() + "] - retrying", ex); 170 } 171 else if (logger.isWarnEnabled()) { 172 logger.warn("Could not connect to remote EJB [" + getJndiName() + "] - retrying"); 173 } 174 return refreshAndRetry(invocation); 175 } 176 else { 177 throw ex; 178 } 179 } 180 181 /** 182 * Refresh the EJB home object and retry the given invocation. 183 * Called by invoke on connect failure. 184 * @param invocation the AOP method invocation 185 * @return the invocation result, if any 186 * @throws Throwable in case of invocation failure 187 * @see #invoke 188 */ 189 protected Object refreshAndRetry(MethodInvocation invocation) throws Throwable { 190 try { 191 refreshHome(); 192 } 193 catch (NamingException ex) { 194 throw new RemoteLookupFailureException("Failed to locate remote EJB [" + getJndiName() + "]", ex); 195 } 196 return doInvoke(invocation); 197 } 198 199 200 /** 201 * Perform the given invocation on the current EJB home. 202 * Template method to be implemented by subclasses. 203 * @param invocation the AOP method invocation 204 * @return the invocation result, if any 205 * @throws Throwable in case of invocation failure 206 * @see #getHome 207 * @see #newSessionBeanInstance 208 */ 209 protected abstract Object doInvoke(MethodInvocation invocation) throws Throwable; 210 211 212 /** 213 * Return a new instance of the stateless session bean. 214 * To be invoked by concrete remote SLSB invoker subclasses. 215 * <p>Can be overridden to change the algorithm. 216 * @throws NamingException if thrown by JNDI 217 * @throws InvocationTargetException if thrown by the create method 218 * @see #create 219 */ 220 protected Object newSessionBeanInstance() throws NamingException, InvocationTargetException { 221 if (logger.isDebugEnabled()) { 222 logger.debug("Trying to create reference to remote EJB"); 223 } 224 Object ejbInstance = create(); 225 if (logger.isDebugEnabled()) { 226 logger.debug("Obtained reference to remote EJB: " + ejbInstance); 227 } 228 return ejbInstance; 229 } 230 231 /** 232 * Remove the given EJB instance. 233 * To be invoked by concrete remote SLSB invoker subclasses. 234 * @param ejb the EJB instance to remove 235 * @see javax.ejb.EJBObject#remove 236 */ 237 protected void removeSessionBeanInstance(EJBObject ejb) { 238 if (ejb != null && !this.homeAsComponent) { 239 try { 240 ejb.remove(); 241 } 242 catch (Throwable ex) { 243 logger.warn("Could not invoke 'remove' on remote EJB proxy", ex); 244 } 245 } 246 } 247 248}