001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.jndi;
018:
019: import javax.naming.NamingException;
020:
021: import org.springframework.aop.framework.ProxyFactory;
022: import org.springframework.beans.factory.BeanClassLoaderAware;
023: import org.springframework.beans.factory.FactoryBean;
024: import org.springframework.util.ClassUtils;
025:
026: /**
027: * {@link org.springframework.beans.factory.FactoryBean} that looks up a
028: * JNDI object. Exposes the object found in JNDI for bean references,
029: * e.g. for data access object's "dataSource" property in case of a
030: * {@link javax.sql.DataSource}.
031: *
032: * <p>The typical usage will be to register this as singleton factory
033: * (e.g. for a certain JNDI-bound DataSource) in an application context,
034: * and give bean references to application services that need it.
035: *
036: * <p>The default behavior is to look up the JNDI object on startup and cache it.
037: * This can be customized through the "lookupOnStartup" and "cache" properties,
038: * using a {@link JndiObjectTargetSource} underneath. Note that you need to specify
039: * a "proxyInterface" in such a scenario, since the actual JNDI object type is not
040: * known in advance.
041: *
042: * <p>Of course, bean classes in a Spring environment may lookup e.g. a DataSource
043: * from JNDI themselves. This class simply enables central configuration of the
044: * JNDI name, and easy switching to non-JNDI alternatives. The latter is
045: * particularly convenient for test setups, reuse in standalone clients, etc.
046: *
047: * <p>Note that switching to e.g. DriverManagerDataSource is just a matter of
048: * configuration: Simply replace the definition of this FactoryBean with a
049: * {@link org.springframework.jdbc.datasource.DriverManagerDataSource} definition!
050: *
051: * @author Juergen Hoeller
052: * @since 22.05.2003
053: * @see #setProxyInterface
054: * @see #setLookupOnStartup
055: * @see #setCache
056: * @see JndiObjectTargetSource
057: */
058: public class JndiObjectFactoryBean extends JndiObjectLocator implements
059: FactoryBean, BeanClassLoaderAware {
060:
061: private Class[] proxyInterfaces;
062:
063: private boolean lookupOnStartup = true;
064:
065: private boolean cache = true;
066:
067: private Object defaultObject;
068:
069: private ClassLoader beanClassLoader = ClassUtils
070: .getDefaultClassLoader();
071:
072: private Object jndiObject;
073:
074: /**
075: * Specify the proxy interface to use for the JNDI object.
076: * <p>Typically used in conjunction with "lookupOnStartup"=false and/or "cache"=false.
077: * Needs to be specified because the actual JNDI object type is not known
078: * in advance in case of a lazy lookup.
079: * @see #setProxyInterfaces
080: * @see #setLookupOnStartup
081: * @see #setCache
082: */
083: public void setProxyInterface(Class proxyInterface) {
084: this .proxyInterfaces = new Class[] { proxyInterface };
085: }
086:
087: /**
088: * Specify multiple proxy interfaces to use for the JNDI object.
089: * <p>Typically used in conjunction with "lookupOnStartup"=false and/or "cache"=false.
090: * Note that proxy interfaces will be autodetected from a specified "expectedType",
091: * if necessary.
092: * @see #setExpectedType
093: * @see #setLookupOnStartup
094: * @see #setCache
095: */
096: public void setProxyInterfaces(Class[] proxyInterfaces) {
097: this .proxyInterfaces = proxyInterfaces;
098: }
099:
100: /**
101: * Set whether to look up the JNDI object on startup. Default is "true".
102: * <p>Can be turned off to allow for late availability of the JNDI object.
103: * In this case, the JNDI object will be fetched on first access.
104: * <p>For a lazy lookup, a proxy interface needs to be specified.
105: * @see #setProxyInterface
106: * @see #setCache
107: */
108: public void setLookupOnStartup(boolean lookupOnStartup) {
109: this .lookupOnStartup = lookupOnStartup;
110: }
111:
112: /**
113: * Set whether to cache the JNDI object once it has been located.
114: * Default is "true".
115: * <p>Can be turned off to allow for hot redeployment of JNDI objects.
116: * In this case, the JNDI object will be fetched for each invocation.
117: * <p>For hot redeployment, a proxy interface needs to be specified.
118: * @see #setProxyInterface
119: * @see #setLookupOnStartup
120: */
121: public void setCache(boolean cache) {
122: this .cache = cache;
123: }
124:
125: /**
126: * Specify a default object to fall back to if the JNDI lookup fails.
127: * Default is none.
128: * <p>This can be an arbitrary bean reference or literal value.
129: * It is typically used for literal values in scenarios where the JNDI environment
130: * might define specific config settings but those are not required to be present.
131: * <p>Note: This is only supported for lookup on startup.
132: * @see #setLookupOnStartup
133: */
134: public void setDefaultObject(Object defaultObject) {
135: this .defaultObject = defaultObject;
136: }
137:
138: public void setBeanClassLoader(ClassLoader classLoader) {
139: this .beanClassLoader = classLoader;
140: }
141:
142: /**
143: * Look up the JNDI object and store it.
144: */
145: public void afterPropertiesSet() throws IllegalArgumentException,
146: NamingException {
147: super .afterPropertiesSet();
148:
149: if (!this .lookupOnStartup || !this .cache) {
150: // We need to create a proxy for this...
151: if (this .proxyInterfaces == null) {
152: Class expectedType = getExpectedType();
153: if (expectedType != null) {
154: if (expectedType.isInterface()) {
155: this .proxyInterfaces = new Class[] { expectedType };
156: } else {
157: this .proxyInterfaces = ClassUtils
158: .getAllInterfacesForClass(expectedType);
159: }
160: }
161: }
162: if (this .proxyInterfaces == null) {
163: throw new IllegalArgumentException(
164: "Cannot deactivate 'lookupOnStartup' or 'cache' without specifying a 'proxyInterface'");
165: }
166: }
167:
168: if (this .proxyInterfaces != null) {
169: if (this .defaultObject != null) {
170: throw new IllegalArgumentException(
171: "'defaultObject' is not supported in combination with 'proxyInterface'");
172: }
173: // We need a proxy and a JndiObjectTargetSource.
174: this .jndiObject = JndiObjectProxyFactory
175: .createJndiObjectProxy(this );
176: }
177:
178: else {
179: if (this .defaultObject != null
180: && getExpectedType() != null
181: && !getExpectedType()
182: .isInstance(this .defaultObject)) {
183: throw new IllegalArgumentException("Default object ["
184: + this .defaultObject + "] of type ["
185: + this .defaultObject.getClass().getName()
186: + "] is not of expected type ["
187: + getExpectedType().getName() + "]");
188: }
189: // Locate specified JNDI object.
190: this .jndiObject = lookupWithFallback();
191: }
192: }
193:
194: /**
195: * Lookup variant that that returns the specified "defaultObject"
196: * (if any) in case of lookup failure.
197: * @return the located object, or the "defaultObject" as fallback
198: * @throws NamingException in case of lookup failure without fallback
199: * @see #setDefaultObject
200: */
201: protected Object lookupWithFallback() throws NamingException {
202: try {
203: return lookup();
204: } catch (TypeMismatchNamingException ex) {
205: // Always let TypeMismatchNamingException through -
206: // we don't want to fall back to the defaultObject in this case.
207: throw ex;
208: } catch (NamingException ex) {
209: if (this .defaultObject != null) {
210: if (logger.isDebugEnabled()) {
211: logger
212: .debug(
213: "JNDI lookup failed - returning specified default object instead",
214: ex);
215: } else if (logger.isInfoEnabled()) {
216: logger
217: .info("JNDI lookup failed - returning specified default object instead: "
218: + ex);
219: }
220: return this .defaultObject;
221: }
222: throw ex;
223: }
224: }
225:
226: /**
227: * Return the singleton JNDI object.
228: */
229: public Object getObject() {
230: return this .jndiObject;
231: }
232:
233: public Class getObjectType() {
234: if (this .proxyInterfaces != null) {
235: if (this .proxyInterfaces.length == 1) {
236: return this .proxyInterfaces[0];
237: } else if (this .proxyInterfaces.length > 1) {
238: return createCompositeInterface(this .proxyInterfaces);
239: }
240: }
241: if (this .jndiObject != null) {
242: return this .jndiObject.getClass();
243: } else {
244: return getExpectedType();
245: }
246: }
247:
248: public boolean isSingleton() {
249: return true;
250: }
251:
252: /**
253: * Create a composite interface Class for the given interfaces,
254: * implementing the given interfaces in one single Class.
255: * <p>The default implementation builds a JDK proxy class for the
256: * given interfaces.
257: * @param interfaces the interfaces to merge
258: * @return the merged interface as Class
259: * @see java.lang.reflect.Proxy#getProxyClass
260: */
261: protected Class createCompositeInterface(Class[] interfaces) {
262: return ClassUtils.createCompositeInterface(interfaces,
263: this .beanClassLoader);
264: }
265:
266: /**
267: * Inner class to just introduce an AOP dependency when actually creating a proxy.
268: */
269: private static class JndiObjectProxyFactory {
270:
271: private static Object createJndiObjectProxy(
272: JndiObjectFactoryBean jof) throws NamingException {
273: // Create a JndiObjectTargetSource that mirrors the JndiObjectFactoryBean's configuration.
274: JndiObjectTargetSource targetSource = new JndiObjectTargetSource();
275: targetSource.setJndiTemplate(jof.getJndiTemplate());
276: targetSource.setJndiName(jof.getJndiName());
277: targetSource.setExpectedType(jof.getExpectedType());
278: targetSource.setResourceRef(jof.isResourceRef());
279: targetSource.setLookupOnStartup(jof.lookupOnStartup);
280: targetSource.setCache(jof.cache);
281: targetSource.afterPropertiesSet();
282:
283: // Create a proxy with JndiObjectFactoryBean's proxy interface and the JndiObjectTargetSource.
284: ProxyFactory proxyFactory = new ProxyFactory();
285: proxyFactory.setInterfaces(jof.proxyInterfaces);
286: proxyFactory.setTargetSource(targetSource);
287: return proxyFactory.getProxy(jof.beanClassLoader);
288: }
289: }
290:
291: }
|