/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.ejb.access;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;

import javax.ejb.EJBHome;
import javax.ejb.EJBObject;
import javax.naming.NamingException;

import org.aopalliance.intercept.MethodInvocation;

import org.springframework.lang.Nullable;
import org.springframework.remoting.RemoteConnectFailureException;
import org.springframework.remoting.RemoteLookupFailureException;

Base class for interceptors proxying remote Stateless Session Beans. Designed for EJB 2.x, but works for EJB 3 Session Beans as well.

Such an interceptor must be the last interceptor in the advice chain. In this case, there is no target object.

Author:Rod Johnson, Juergen Hoeller
/** * Base class for interceptors proxying remote Stateless Session Beans. * Designed for EJB 2.x, but works for EJB 3 Session Beans as well. * * <p>Such an interceptor must be the last interceptor in the advice chain. * In this case, there is no target object. * * @author Rod Johnson * @author Juergen Hoeller */
public abstract class AbstractRemoteSlsbInvokerInterceptor extends AbstractSlsbInvokerInterceptor { private boolean refreshHomeOnConnectFailure = false; private volatile boolean homeAsComponent;
Set whether to refresh the EJB home on connect failure. Default is "false".

Can be turned on to allow for hot restart of the EJB server. If a cached EJB home throws an RMI exception that indicates a remote connect failure, a fresh home will be fetched and the invocation will be retried.

See Also:
/** * Set whether to refresh the EJB home on connect failure. * Default is "false". * <p>Can be turned on to allow for hot restart of the EJB server. * If a cached EJB home throws an RMI exception that indicates a * remote connect failure, a fresh home will be fetched and the * invocation will be retried. * @see java.rmi.ConnectException * @see java.rmi.ConnectIOException * @see java.rmi.NoSuchObjectException */
public void setRefreshHomeOnConnectFailure(boolean refreshHomeOnConnectFailure) { this.refreshHomeOnConnectFailure = refreshHomeOnConnectFailure; } @Override protected boolean isHomeRefreshable() { return this.refreshHomeOnConnectFailure; }
Check for EJB3-style home object that serves as EJB component directly.
/** * Check for EJB3-style home object that serves as EJB component directly. */
@Override protected Method getCreateMethod(Object home) throws EjbAccessException { if (this.homeAsComponent) { return null; } if (!(home instanceof EJBHome)) { // An EJB3 Session Bean... this.homeAsComponent = true; return null; } return super.getCreateMethod(home); }
Fetches an EJB home object and delegates to doInvoke.

If configured to refresh on connect failure, it will call refreshAndRetry on corresponding RMI exceptions.

See Also:
/** * Fetches an EJB home object and delegates to {@code doInvoke}. * <p>If configured to refresh on connect failure, it will call * {@link #refreshAndRetry} on corresponding RMI exceptions. * @see #getHome * @see #doInvoke * @see #refreshAndRetry */
@Override @Nullable public Object invokeInContext(MethodInvocation invocation) throws Throwable { try { return doInvoke(invocation); } catch (RemoteConnectFailureException ex) { return handleRemoteConnectFailure(invocation, ex); } catch (RemoteException ex) { if (isConnectFailure(ex)) { return handleRemoteConnectFailure(invocation, ex); } else { throw ex; } } }
Determine whether the given RMI exception indicates a connect failure.

The default implementation delegates to RmiClientInterceptorUtils.

Params:
  • ex – the RMI exception to check
See Also:
Returns:whether the exception should be treated as connect failure
/** * Determine whether the given RMI exception indicates a connect failure. * <p>The default implementation delegates to RmiClientInterceptorUtils. * @param ex the RMI exception to check * @return whether the exception should be treated as connect failure * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure */
@SuppressWarnings("deprecation") protected boolean isConnectFailure(RemoteException ex) { return org.springframework.remoting.rmi.RmiClientInterceptorUtils.isConnectFailure(ex); } @Nullable private Object handleRemoteConnectFailure(MethodInvocation invocation, Exception ex) throws Throwable { if (this.refreshHomeOnConnectFailure) { if (logger.isDebugEnabled()) { logger.debug("Could not connect to remote EJB [" + getJndiName() + "] - retrying", ex); } else if (logger.isWarnEnabled()) { logger.warn("Could not connect to remote EJB [" + getJndiName() + "] - retrying"); } return refreshAndRetry(invocation); } else { throw ex; } }
Refresh the EJB home object and retry the given invocation. Called by invoke on connect failure.
Params:
  • invocation – the AOP method invocation
Throws:
See Also:
Returns:the invocation result, if any
/** * Refresh the EJB home object and retry the given invocation. * Called by invoke on connect failure. * @param invocation the AOP method invocation * @return the invocation result, if any * @throws Throwable in case of invocation failure * @see #invoke */
@Nullable protected Object refreshAndRetry(MethodInvocation invocation) throws Throwable { try { refreshHome(); } catch (NamingException ex) { throw new RemoteLookupFailureException("Failed to locate remote EJB [" + getJndiName() + "]", ex); } return doInvoke(invocation); }
Perform the given invocation on the current EJB home. Template method to be implemented by subclasses.
Params:
  • invocation – the AOP method invocation
Throws:
See Also:
Returns:the invocation result, if any
/** * Perform the given invocation on the current EJB home. * Template method to be implemented by subclasses. * @param invocation the AOP method invocation * @return the invocation result, if any * @throws Throwable in case of invocation failure * @see #getHome * @see #newSessionBeanInstance */
@Nullable protected abstract Object doInvoke(MethodInvocation invocation) throws Throwable;
Return a new instance of the stateless session bean. To be invoked by concrete remote SLSB invoker subclasses.

Can be overridden to change the algorithm.

Throws:
See Also:
/** * Return a new instance of the stateless session bean. * To be invoked by concrete remote SLSB invoker subclasses. * <p>Can be overridden to change the algorithm. * @throws NamingException if thrown by JNDI * @throws InvocationTargetException if thrown by the create method * @see #create */
protected Object newSessionBeanInstance() throws NamingException, InvocationTargetException { if (logger.isDebugEnabled()) { logger.debug("Trying to create reference to remote EJB"); } Object ejbInstance = create(); if (logger.isDebugEnabled()) { logger.debug("Obtained reference to remote EJB: " + ejbInstance); } return ejbInstance; }
Remove the given EJB instance. To be invoked by concrete remote SLSB invoker subclasses.
Params:
  • ejb – the EJB instance to remove
See Also:
  • remove.remove
/** * Remove the given EJB instance. * To be invoked by concrete remote SLSB invoker subclasses. * @param ejb the EJB instance to remove * @see javax.ejb.EJBObject#remove */
protected void removeSessionBeanInstance(@Nullable EJBObject ejb) { if (ejb != null && !this.homeAsComponent) { try { ejb.remove(); } catch (Throwable ex) { logger.warn("Could not invoke 'remove' on remote EJB proxy", ex); } } } }