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.rmi.RemoteException; 021import javax.ejb.CreateException; 022import javax.ejb.EJBObject; 023import javax.naming.NamingException; 024 025import org.aopalliance.intercept.MethodInvocation; 026 027import org.springframework.beans.factory.DisposableBean; 028import org.springframework.remoting.RemoteLookupFailureException; 029import org.springframework.remoting.rmi.RmiClientInterceptorUtils; 030 031/** 032 * Basic invoker for a remote Stateless Session Bean. 033 * Designed for EJB 2.x, but works for EJB 3 Session Beans as well. 034 * 035 * <p>"Creates" a new EJB instance for each invocation, or caches the session 036 * bean instance for all invocations (see {@link #setCacheSessionBean}). 037 * See {@link org.springframework.jndi.JndiObjectLocator} for info on 038 * how to specify the JNDI location of the target EJB. 039 * 040 * <p>In a bean container, this class is normally best used as a singleton. However, 041 * if that bean container pre-instantiates singletons (as do the XML ApplicationContext 042 * variants) you may have a problem if the bean container is loaded before the EJB 043 * container loads the target EJB. That is because by default the JNDI lookup will be 044 * performed in the init method of this class and cached, but the EJB will not have been 045 * bound at the target location yet. The best solution is to set the "lookupHomeOnStartup" 046 * property to "false", in which case the home will be fetched on first access to the EJB. 047 * (This flag is only true by default for backwards compatibility reasons). 048 * 049 * <p>This invoker is typically used with an RMI business interface, which serves 050 * as super-interface of the EJB component interface. Alternatively, this invoker 051 * can also proxy a remote SLSB with a matching non-RMI business interface, i.e. an 052 * interface that mirrors the EJB business methods but does not declare RemoteExceptions. 053 * In the latter case, RemoteExceptions thrown by the EJB stub will automatically get 054 * converted to Spring's unchecked RemoteAccessException. 055 * 056 * @author Rod Johnson 057 * @author Juergen Hoeller 058 * @since 09.05.2003 059 * @see org.springframework.remoting.RemoteAccessException 060 * @see AbstractSlsbInvokerInterceptor#setLookupHomeOnStartup 061 * @see AbstractSlsbInvokerInterceptor#setCacheHome 062 * @see AbstractRemoteSlsbInvokerInterceptor#setRefreshHomeOnConnectFailure 063 */ 064public class SimpleRemoteSlsbInvokerInterceptor extends AbstractRemoteSlsbInvokerInterceptor 065 implements DisposableBean { 066 067 private boolean cacheSessionBean = false; 068 069 private Object beanInstance; 070 071 private final Object beanInstanceMonitor = new Object(); 072 073 074 /** 075 * Set whether to cache the actual session bean object. 076 * <p>Off by default for standard EJB compliance. Turn this flag 077 * on to optimize session bean access for servers that are 078 * known to allow for caching the actual session bean object. 079 * @see #setCacheHome 080 */ 081 public void setCacheSessionBean(boolean cacheSessionBean) { 082 this.cacheSessionBean = cacheSessionBean; 083 } 084 085 086 /** 087 * This implementation "creates" a new EJB instance for each invocation. 088 * Can be overridden for custom invocation strategies. 089 * <p>Alternatively, override {@link #getSessionBeanInstance} and 090 * {@link #releaseSessionBeanInstance} to change EJB instance creation, 091 * for example to hold a single shared EJB component instance. 092 */ 093 @Override 094 protected Object doInvoke(MethodInvocation invocation) throws Throwable { 095 Object ejb = null; 096 try { 097 ejb = getSessionBeanInstance(); 098 return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, ejb); 099 } 100 catch (NamingException ex) { 101 throw new RemoteLookupFailureException("Failed to locate remote EJB [" + getJndiName() + "]", ex); 102 } 103 catch (InvocationTargetException ex) { 104 Throwable targetEx = ex.getTargetException(); 105 if (targetEx instanceof RemoteException) { 106 RemoteException rex = (RemoteException) targetEx; 107 throw RmiClientInterceptorUtils.convertRmiAccessException( 108 invocation.getMethod(), rex, isConnectFailure(rex), getJndiName()); 109 } 110 else if (targetEx instanceof CreateException) { 111 throw RmiClientInterceptorUtils.convertRmiAccessException( 112 invocation.getMethod(), targetEx, "Could not create remote EJB [" + getJndiName() + "]"); 113 } 114 throw targetEx; 115 } 116 finally { 117 if (ejb instanceof EJBObject) { 118 releaseSessionBeanInstance((EJBObject) ejb); 119 } 120 } 121 } 122 123 /** 124 * Return an EJB component instance to delegate the call to. 125 * <p>The default implementation delegates to {@link #newSessionBeanInstance}. 126 * @return the EJB component instance 127 * @throws NamingException if thrown by JNDI 128 * @throws InvocationTargetException if thrown by the create method 129 * @see #newSessionBeanInstance 130 */ 131 protected Object getSessionBeanInstance() throws NamingException, InvocationTargetException { 132 if (this.cacheSessionBean) { 133 synchronized (this.beanInstanceMonitor) { 134 if (this.beanInstance == null) { 135 this.beanInstance = newSessionBeanInstance(); 136 } 137 return this.beanInstance; 138 } 139 } 140 else { 141 return newSessionBeanInstance(); 142 } 143 } 144 145 /** 146 * Release the given EJB instance. 147 * <p>The default implementation delegates to {@link #removeSessionBeanInstance}. 148 * @param ejb the EJB component instance to release 149 * @see #removeSessionBeanInstance 150 */ 151 protected void releaseSessionBeanInstance(EJBObject ejb) { 152 if (!this.cacheSessionBean) { 153 removeSessionBeanInstance(ejb); 154 } 155 } 156 157 /** 158 * Reset the cached session bean instance, if necessary. 159 */ 160 @Override 161 protected void refreshHome() throws NamingException { 162 super.refreshHome(); 163 if (this.cacheSessionBean) { 164 synchronized (this.beanInstanceMonitor) { 165 this.beanInstance = null; 166 } 167 } 168 } 169 170 /** 171 * Remove the cached session bean instance, if necessary. 172 */ 173 @Override 174 public void destroy() { 175 if (this.cacheSessionBean) { 176 synchronized (this.beanInstanceMonitor) { 177 if (this.beanInstance instanceof EJBObject) { 178 removeSessionBeanInstance((EJBObject) this.beanInstance); 179 } 180 } 181 } 182 } 183 184}