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