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.jndi; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Modifier; 021 022import javax.naming.Context; 023import javax.naming.NamingException; 024 025import org.aopalliance.intercept.MethodInterceptor; 026import org.aopalliance.intercept.MethodInvocation; 027 028import org.springframework.aop.framework.ProxyFactory; 029import org.springframework.beans.SimpleTypeConverter; 030import org.springframework.beans.TypeConverter; 031import org.springframework.beans.TypeMismatchException; 032import org.springframework.beans.factory.BeanClassLoaderAware; 033import org.springframework.beans.factory.BeanFactory; 034import org.springframework.beans.factory.BeanFactoryAware; 035import org.springframework.beans.factory.FactoryBean; 036import org.springframework.beans.factory.config.ConfigurableBeanFactory; 037import org.springframework.lang.Nullable; 038import org.springframework.util.Assert; 039import org.springframework.util.ClassUtils; 040 041/** 042 * {@link org.springframework.beans.factory.FactoryBean} that looks up a 043 * JNDI object. Exposes the object found in JNDI for bean references, 044 * e.g. for data access object's "dataSource" property in case of a 045 * {@link javax.sql.DataSource}. 046 * 047 * <p>The typical usage will be to register this as singleton factory 048 * (e.g. for a certain JNDI-bound DataSource) in an application context, 049 * and give bean references to application services that need it. 050 * 051 * <p>The default behavior is to look up the JNDI object on startup and cache it. 052 * This can be customized through the "lookupOnStartup" and "cache" properties, 053 * using a {@link JndiObjectTargetSource} underneath. Note that you need to specify 054 * a "proxyInterface" in such a scenario, since the actual JNDI object type is not 055 * known in advance. 056 * 057 * <p>Of course, bean classes in a Spring environment may lookup e.g. a DataSource 058 * from JNDI themselves. This class simply enables central configuration of the 059 * JNDI name, and easy switching to non-JNDI alternatives. The latter is 060 * particularly convenient for test setups, reuse in standalone clients, etc. 061 * 062 * <p>Note that switching to e.g. DriverManagerDataSource is just a matter of 063 * configuration: Simply replace the definition of this FactoryBean with a 064 * {@link org.springframework.jdbc.datasource.DriverManagerDataSource} definition! 065 * 066 * @author Juergen Hoeller 067 * @since 22.05.2003 068 * @see #setProxyInterface 069 * @see #setLookupOnStartup 070 * @see #setCache 071 * @see JndiObjectTargetSource 072 */ 073public class JndiObjectFactoryBean extends JndiObjectLocator 074 implements FactoryBean<Object>, BeanFactoryAware, BeanClassLoaderAware { 075 076 @Nullable 077 private Class<?>[] proxyInterfaces; 078 079 private boolean lookupOnStartup = true; 080 081 private boolean cache = true; 082 083 private boolean exposeAccessContext = false; 084 085 @Nullable 086 private Object defaultObject; 087 088 @Nullable 089 private ConfigurableBeanFactory beanFactory; 090 091 @Nullable 092 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 093 094 @Nullable 095 private Object jndiObject; 096 097 098 /** 099 * Specify the proxy interface to use for the JNDI object. 100 * <p>Typically used in conjunction with "lookupOnStartup"=false and/or "cache"=false. 101 * Needs to be specified because the actual JNDI object type is not known 102 * in advance in case of a lazy lookup. 103 * @see #setProxyInterfaces 104 * @see #setLookupOnStartup 105 * @see #setCache 106 */ 107 public void setProxyInterface(Class<?> proxyInterface) { 108 this.proxyInterfaces = new Class<?>[] {proxyInterface}; 109 } 110 111 /** 112 * Specify multiple proxy interfaces to use for the JNDI object. 113 * <p>Typically used in conjunction with "lookupOnStartup"=false and/or "cache"=false. 114 * Note that proxy interfaces will be autodetected from a specified "expectedType", 115 * if necessary. 116 * @see #setExpectedType 117 * @see #setLookupOnStartup 118 * @see #setCache 119 */ 120 public void setProxyInterfaces(Class<?>... proxyInterfaces) { 121 this.proxyInterfaces = proxyInterfaces; 122 } 123 124 /** 125 * Set whether to look up the JNDI object on startup. Default is "true". 126 * <p>Can be turned off to allow for late availability of the JNDI object. 127 * In this case, the JNDI object will be fetched on first access. 128 * <p>For a lazy lookup, a proxy interface needs to be specified. 129 * @see #setProxyInterface 130 * @see #setCache 131 */ 132 public void setLookupOnStartup(boolean lookupOnStartup) { 133 this.lookupOnStartup = lookupOnStartup; 134 } 135 136 /** 137 * Set whether to cache the JNDI object once it has been located. 138 * Default is "true". 139 * <p>Can be turned off to allow for hot redeployment of JNDI objects. 140 * In this case, the JNDI object will be fetched for each invocation. 141 * <p>For hot redeployment, a proxy interface needs to be specified. 142 * @see #setProxyInterface 143 * @see #setLookupOnStartup 144 */ 145 public void setCache(boolean cache) { 146 this.cache = cache; 147 } 148 149 /** 150 * Set whether to expose the JNDI environment context for all access to the target 151 * object, i.e. for all method invocations on the exposed object reference. 152 * <p>Default is "false", i.e. to only expose the JNDI context for object lookup. 153 * Switch this flag to "true" in order to expose the JNDI environment (including 154 * the authorization context) for each method invocation, as needed by WebLogic 155 * for JNDI-obtained factories (e.g. JDBC DataSource, JMS ConnectionFactory) 156 * with authorization requirements. 157 */ 158 public void setExposeAccessContext(boolean exposeAccessContext) { 159 this.exposeAccessContext = exposeAccessContext; 160 } 161 162 /** 163 * Specify a default object to fall back to if the JNDI lookup fails. 164 * Default is none. 165 * <p>This can be an arbitrary bean reference or literal value. 166 * It is typically used for literal values in scenarios where the JNDI environment 167 * might define specific config settings but those are not required to be present. 168 * <p>Note: This is only supported for lookup on startup. 169 * If specified together with {@link #setExpectedType}, the specified value 170 * needs to be either of that type or convertible to it. 171 * @see #setLookupOnStartup 172 * @see ConfigurableBeanFactory#getTypeConverter() 173 * @see SimpleTypeConverter 174 */ 175 public void setDefaultObject(Object defaultObject) { 176 this.defaultObject = defaultObject; 177 } 178 179 @Override 180 public void setBeanFactory(BeanFactory beanFactory) { 181 if (beanFactory instanceof ConfigurableBeanFactory) { 182 // Just optional - for getting a specifically configured TypeConverter if needed. 183 // We'll simply fall back to a SimpleTypeConverter if no specific one available. 184 this.beanFactory = (ConfigurableBeanFactory) beanFactory; 185 } 186 } 187 188 @Override 189 public void setBeanClassLoader(ClassLoader classLoader) { 190 this.beanClassLoader = classLoader; 191 } 192 193 194 /** 195 * Look up the JNDI object and store it. 196 */ 197 @Override 198 public void afterPropertiesSet() throws IllegalArgumentException, NamingException { 199 super.afterPropertiesSet(); 200 201 if (this.proxyInterfaces != null || !this.lookupOnStartup || !this.cache || this.exposeAccessContext) { 202 // We need to create a proxy for this... 203 if (this.defaultObject != null) { 204 throw new IllegalArgumentException( 205 "'defaultObject' is not supported in combination with 'proxyInterface'"); 206 } 207 // We need a proxy and a JndiObjectTargetSource. 208 this.jndiObject = JndiObjectProxyFactory.createJndiObjectProxy(this); 209 } 210 else { 211 if (this.defaultObject != null && getExpectedType() != null && 212 !getExpectedType().isInstance(this.defaultObject)) { 213 TypeConverter converter = (this.beanFactory != null ? 214 this.beanFactory.getTypeConverter() : new SimpleTypeConverter()); 215 try { 216 this.defaultObject = converter.convertIfNecessary(this.defaultObject, getExpectedType()); 217 } 218 catch (TypeMismatchException ex) { 219 throw new IllegalArgumentException("Default object [" + this.defaultObject + "] of type [" + 220 this.defaultObject.getClass().getName() + "] is not of expected type [" + 221 getExpectedType().getName() + "] and cannot be converted either", ex); 222 } 223 } 224 // Locate specified JNDI object. 225 this.jndiObject = lookupWithFallback(); 226 } 227 } 228 229 /** 230 * Lookup variant that returns the specified "defaultObject" 231 * (if any) in case of lookup failure. 232 * @return the located object, or the "defaultObject" as fallback 233 * @throws NamingException in case of lookup failure without fallback 234 * @see #setDefaultObject 235 */ 236 protected Object lookupWithFallback() throws NamingException { 237 ClassLoader originalClassLoader = ClassUtils.overrideThreadContextClassLoader(this.beanClassLoader); 238 try { 239 return lookup(); 240 } 241 catch (TypeMismatchNamingException ex) { 242 // Always let TypeMismatchNamingException through - 243 // we don't want to fall back to the defaultObject in this case. 244 throw ex; 245 } 246 catch (NamingException ex) { 247 if (this.defaultObject != null) { 248 if (logger.isTraceEnabled()) { 249 logger.trace("JNDI lookup failed - returning specified default object instead", ex); 250 } 251 else if (logger.isDebugEnabled()) { 252 logger.debug("JNDI lookup failed - returning specified default object instead: " + ex); 253 } 254 return this.defaultObject; 255 } 256 throw ex; 257 } 258 finally { 259 if (originalClassLoader != null) { 260 Thread.currentThread().setContextClassLoader(originalClassLoader); 261 } 262 } 263 } 264 265 266 /** 267 * Return the singleton JNDI object. 268 */ 269 @Override 270 @Nullable 271 public Object getObject() { 272 return this.jndiObject; 273 } 274 275 @Override 276 public Class<?> getObjectType() { 277 if (this.proxyInterfaces != null) { 278 if (this.proxyInterfaces.length == 1) { 279 return this.proxyInterfaces[0]; 280 } 281 else if (this.proxyInterfaces.length > 1) { 282 return createCompositeInterface(this.proxyInterfaces); 283 } 284 } 285 if (this.jndiObject != null) { 286 return this.jndiObject.getClass(); 287 } 288 else { 289 return getExpectedType(); 290 } 291 } 292 293 @Override 294 public boolean isSingleton() { 295 return true; 296 } 297 298 299 /** 300 * Create a composite interface Class for the given interfaces, 301 * implementing the given interfaces in one single Class. 302 * <p>The default implementation builds a JDK proxy class for the 303 * given interfaces. 304 * @param interfaces the interfaces to merge 305 * @return the merged interface as Class 306 * @see java.lang.reflect.Proxy#getProxyClass 307 */ 308 protected Class<?> createCompositeInterface(Class<?>[] interfaces) { 309 return ClassUtils.createCompositeInterface(interfaces, this.beanClassLoader); 310 } 311 312 313 /** 314 * Inner class to just introduce an AOP dependency when actually creating a proxy. 315 */ 316 private static class JndiObjectProxyFactory { 317 318 private static Object createJndiObjectProxy(JndiObjectFactoryBean jof) throws NamingException { 319 // Create a JndiObjectTargetSource that mirrors the JndiObjectFactoryBean's configuration. 320 JndiObjectTargetSource targetSource = new JndiObjectTargetSource(); 321 targetSource.setJndiTemplate(jof.getJndiTemplate()); 322 String jndiName = jof.getJndiName(); 323 Assert.state(jndiName != null, "No JNDI name specified"); 324 targetSource.setJndiName(jndiName); 325 targetSource.setExpectedType(jof.getExpectedType()); 326 targetSource.setResourceRef(jof.isResourceRef()); 327 targetSource.setLookupOnStartup(jof.lookupOnStartup); 328 targetSource.setCache(jof.cache); 329 targetSource.afterPropertiesSet(); 330 331 // Create a proxy with JndiObjectFactoryBean's proxy interface and the JndiObjectTargetSource. 332 ProxyFactory proxyFactory = new ProxyFactory(); 333 if (jof.proxyInterfaces != null) { 334 proxyFactory.setInterfaces(jof.proxyInterfaces); 335 } 336 else { 337 Class<?> targetClass = targetSource.getTargetClass(); 338 if (targetClass == null) { 339 throw new IllegalStateException( 340 "Cannot deactivate 'lookupOnStartup' without specifying a 'proxyInterface' or 'expectedType'"); 341 } 342 Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(targetClass, jof.beanClassLoader); 343 for (Class<?> ifc : ifcs) { 344 if (Modifier.isPublic(ifc.getModifiers())) { 345 proxyFactory.addInterface(ifc); 346 } 347 } 348 } 349 if (jof.exposeAccessContext) { 350 proxyFactory.addAdvice(new JndiContextExposingInterceptor(jof.getJndiTemplate())); 351 } 352 proxyFactory.setTargetSource(targetSource); 353 return proxyFactory.getProxy(jof.beanClassLoader); 354 } 355 } 356 357 358 /** 359 * Interceptor that exposes the JNDI context for all method invocations, 360 * according to JndiObjectFactoryBean's "exposeAccessContext" flag. 361 */ 362 private static class JndiContextExposingInterceptor implements MethodInterceptor { 363 364 private final JndiTemplate jndiTemplate; 365 366 public JndiContextExposingInterceptor(JndiTemplate jndiTemplate) { 367 this.jndiTemplate = jndiTemplate; 368 } 369 370 @Override 371 public Object invoke(MethodInvocation invocation) throws Throwable { 372 Context ctx = (isEligible(invocation.getMethod()) ? this.jndiTemplate.getContext() : null); 373 try { 374 return invocation.proceed(); 375 } 376 finally { 377 this.jndiTemplate.releaseContext(ctx); 378 } 379 } 380 381 protected boolean isEligible(Method method) { 382 return (Object.class != method.getDeclaringClass()); 383 } 384 } 385 386}