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}