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}