001/*
002 * Copyright 2002-2018 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;
021
022import javax.naming.Context;
023import javax.naming.NamingException;
024
025import org.aopalliance.intercept.MethodInterceptor;
026import org.aopalliance.intercept.MethodInvocation;
027
028import org.springframework.jndi.JndiObjectLocator;
029import org.springframework.lang.Nullable;
030
031/**
032 * Base class for AOP interceptors invoking local or remote Stateless Session Beans.
033 * Designed for EJB 2.x, but works for EJB 3 Session Beans as well.
034 *
035 * <p>Such an interceptor must be the last interceptor in the advice chain.
036 * In this case, there is no direct target object: The call is handled in a
037 * special way, getting executed on an EJB instance retrieved via an EJB home.
038 *
039 * @author Rod Johnson
040 * @author Juergen Hoeller
041 */
042public abstract class AbstractSlsbInvokerInterceptor extends JndiObjectLocator
043                implements MethodInterceptor {
044
045        private boolean lookupHomeOnStartup = true;
046
047        private boolean cacheHome = true;
048
049        private boolean exposeAccessContext = false;
050
051        /**
052         * The EJB's home object, potentially cached.
053         * The type must be Object as it could be either EJBHome or EJBLocalHome.
054         */
055        @Nullable
056        private Object cachedHome;
057
058        /**
059         * The no-arg create() method required on EJB homes, potentially cached.
060         */
061        @Nullable
062        private Method createMethod;
063
064        private final Object homeMonitor = new Object();
065
066
067        /**
068         * Set whether to look up the EJB home object on startup.
069         * Default is "true".
070         * <p>Can be turned off to allow for late start of the EJB server.
071         * In this case, the EJB home object will be fetched on first access.
072         * @see #setCacheHome
073         */
074        public void setLookupHomeOnStartup(boolean lookupHomeOnStartup) {
075                this.lookupHomeOnStartup = lookupHomeOnStartup;
076        }
077
078        /**
079         * Set whether to cache the EJB home object once it has been located.
080         * Default is "true".
081         * <p>Can be turned off to allow for hot restart of the EJB server.
082         * In this case, the EJB home object will be fetched for each invocation.
083         * @see #setLookupHomeOnStartup
084         */
085        public void setCacheHome(boolean cacheHome) {
086                this.cacheHome = cacheHome;
087        }
088
089        /**
090         * Set whether to expose the JNDI environment context for all access to the target
091         * EJB, i.e. for all method invocations on the exposed object reference.
092         * <p>Default is "false", i.e. to only expose the JNDI context for object lookup.
093         * Switch this flag to "true" in order to expose the JNDI environment (including
094         * the authorization context) for each EJB invocation, as needed by WebLogic
095         * for EJBs with authorization requirements.
096         */
097        public void setExposeAccessContext(boolean exposeAccessContext) {
098                this.exposeAccessContext = exposeAccessContext;
099        }
100
101
102        /**
103         * Fetches EJB home on startup, if necessary.
104         * @see #setLookupHomeOnStartup
105         * @see #refreshHome
106         */
107        @Override
108        public void afterPropertiesSet() throws NamingException {
109                super.afterPropertiesSet();
110                if (this.lookupHomeOnStartup) {
111                        // look up EJB home and create method
112                        refreshHome();
113                }
114        }
115
116        /**
117         * Refresh the cached home object, if applicable.
118         * Also caches the create method on the home object.
119         * @throws NamingException if thrown by the JNDI lookup
120         * @see #lookup
121         * @see #getCreateMethod
122         */
123        protected void refreshHome() throws NamingException {
124                synchronized (this.homeMonitor) {
125                        Object home = lookup();
126                        if (this.cacheHome) {
127                                this.cachedHome = home;
128                                this.createMethod = getCreateMethod(home);
129                        }
130                }
131        }
132
133        /**
134         * Determine the create method of the given EJB home object.
135         * @param home the EJB home object
136         * @return the create method
137         * @throws EjbAccessException if the method couldn't be retrieved
138         */
139        @Nullable
140        protected Method getCreateMethod(Object home) throws EjbAccessException {
141                try {
142                        // Cache the EJB create() method that must be declared on the home interface.
143                        return home.getClass().getMethod("create");
144                }
145                catch (NoSuchMethodException ex) {
146                        throw new EjbAccessException("EJB home [" + home + "] has no no-arg create() method");
147                }
148        }
149
150        /**
151         * Return the EJB home object to use. Called for each invocation.
152         * <p>Default implementation returns the home created on initialization,
153         * if any; else, it invokes lookup to get a new proxy for each invocation.
154         * <p>Can be overridden in subclasses, for example to cache a home object
155         * for a given amount of time before recreating it, or to test the home
156         * object whether it is still alive.
157         * @return the EJB home object to use for an invocation
158         * @throws NamingException if proxy creation failed
159         * @see #lookup
160         * @see #getCreateMethod
161         */
162        protected Object getHome() throws NamingException {
163                if (!this.cacheHome || (this.lookupHomeOnStartup && !isHomeRefreshable())) {
164                        return (this.cachedHome != null ? this.cachedHome : lookup());
165                }
166                else {
167                        synchronized (this.homeMonitor) {
168                                if (this.cachedHome == null) {
169                                        this.cachedHome = lookup();
170                                        this.createMethod = getCreateMethod(this.cachedHome);
171                                }
172                                return this.cachedHome;
173                        }
174                }
175        }
176
177        /**
178         * Return whether the cached EJB home object is potentially
179         * subject to on-demand refreshing. Default is "false".
180         */
181        protected boolean isHomeRefreshable() {
182                return false;
183        }
184
185
186        /**
187         * Prepares the thread context if necessary, and delegates to
188         * {@link #invokeInContext}.
189         */
190        @Override
191        @Nullable
192        public Object invoke(MethodInvocation invocation) throws Throwable {
193                Context ctx = (this.exposeAccessContext ? getJndiTemplate().getContext() : null);
194                try {
195                        return invokeInContext(invocation);
196                }
197                finally {
198                        getJndiTemplate().releaseContext(ctx);
199                }
200        }
201
202        /**
203         * Perform the given invocation on the current EJB home,
204         * within the thread context being prepared accordingly.
205         * Template method to be implemented by subclasses.
206         * @param invocation the AOP method invocation
207         * @return the invocation result, if any
208         * @throws Throwable in case of invocation failure
209         */
210        @Nullable
211        protected abstract Object invokeInContext(MethodInvocation invocation) throws Throwable;
212
213
214        /**
215         * Invokes the {@code create()} method on the cached EJB home object.
216         * @return a new EJBObject or EJBLocalObject
217         * @throws NamingException if thrown by JNDI
218         * @throws InvocationTargetException if thrown by the create method
219         */
220        protected Object create() throws NamingException, InvocationTargetException {
221                try {
222                        Object home = getHome();
223                        Method createMethodToUse = this.createMethod;
224                        if (createMethodToUse == null) {
225                                createMethodToUse = getCreateMethod(home);
226                        }
227                        if (createMethodToUse == null) {
228                                return home;
229                        }
230                        // Invoke create() method on EJB home object.
231                        return createMethodToUse.invoke(home, (Object[]) null);
232                }
233                catch (IllegalAccessException ex) {
234                        throw new EjbAccessException("Could not access EJB home create() method", ex);
235                }
236        }
237
238}