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