001/*
002 * Copyright 2002-2012 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.util.Hashtable;
020import java.util.Properties;
021import javax.naming.Context;
022import javax.naming.InitialContext;
023import javax.naming.NameNotFoundException;
024import javax.naming.NamingException;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.util.CollectionUtils;
030
031/**
032 * Helper class that simplifies JNDI operations. It provides methods to lookup and
033 * bind objects, and allows implementations of the {@link JndiCallback} interface
034 * to perform any operation they like with a JNDI naming context provided.
035 *
036 * @author Rod Johnson
037 * @author Juergen Hoeller
038 * @see JndiCallback
039 * @see #execute
040 */
041public class JndiTemplate {
042
043        protected final Log logger = LogFactory.getLog(getClass());
044
045        private Properties environment;
046
047
048        /**
049         * Create a new JndiTemplate instance.
050         */
051        public JndiTemplate() {
052        }
053
054        /**
055         * Create a new JndiTemplate instance, using the given environment.
056         */
057        public JndiTemplate(Properties environment) {
058                this.environment = environment;
059        }
060
061
062        /**
063         * Set the environment for the JNDI InitialContext.
064         */
065        public void setEnvironment(Properties environment) {
066                this.environment = environment;
067        }
068
069        /**
070         * Return the environment for the JNDI InitialContext, if any.
071         */
072        public Properties getEnvironment() {
073                return this.environment;
074        }
075
076
077        /**
078         * Execute the given JNDI context callback implementation.
079         * @param contextCallback JndiCallback implementation
080         * @return a result object returned by the callback, or {@code null}
081         * @throws NamingException thrown by the callback implementation
082         * @see #createInitialContext
083         */
084        public <T> T execute(JndiCallback<T> contextCallback) throws NamingException {
085                Context ctx = getContext();
086                try {
087                        return contextCallback.doInContext(ctx);
088                }
089                finally {
090                        releaseContext(ctx);
091                }
092        }
093
094        /**
095         * Obtain a JNDI context corresponding to this template's configuration.
096         * Called by {@link #execute}; may also be called directly.
097         * <p>The default implementation delegates to {@link #createInitialContext()}.
098         * @return the JNDI context (never {@code null})
099         * @throws NamingException if context retrieval failed
100         * @see #releaseContext
101         */
102        public Context getContext() throws NamingException {
103                return createInitialContext();
104        }
105
106        /**
107         * Release a JNDI context as obtained from {@link #getContext()}.
108         * @param ctx the JNDI context to release (may be {@code null})
109         * @see #getContext
110         */
111        public void releaseContext(Context ctx) {
112                if (ctx != null) {
113                        try {
114                                ctx.close();
115                        }
116                        catch (NamingException ex) {
117                                logger.debug("Could not close JNDI InitialContext", ex);
118                        }
119                }
120        }
121
122        /**
123         * Create a new JNDI initial context. Invoked by {@link #getContext}.
124         * <p>The default implementation use this template's environment settings.
125         * Can be subclassed for custom contexts, e.g. for testing.
126         * @return the initial Context instance
127         * @throws NamingException in case of initialization errors
128         */
129        protected Context createInitialContext() throws NamingException {
130                Hashtable<?, ?> icEnv = null;
131                Properties env = getEnvironment();
132                if (env != null) {
133                        icEnv = new Hashtable<Object, Object>(env.size());
134                        CollectionUtils.mergePropertiesIntoMap(env, icEnv);
135                }
136                return new InitialContext(icEnv);
137        }
138
139
140        /**
141         * Look up the object with the given name in the current JNDI context.
142         * @param name the JNDI name of the object
143         * @return object found (cannot be {@code null}; if a not so well-behaved
144         * JNDI implementations returns null, a NamingException gets thrown)
145         * @throws NamingException if there is no object with the given
146         * name bound to JNDI
147         */
148        public Object lookup(final String name) throws NamingException {
149                if (logger.isDebugEnabled()) {
150                        logger.debug("Looking up JNDI object with name [" + name + "]");
151                }
152                return execute(new JndiCallback<Object>() {
153                        @Override
154                        public Object doInContext(Context ctx) throws NamingException {
155                                Object located = ctx.lookup(name);
156                                if (located == null) {
157                                        throw new NameNotFoundException(
158                                                        "JNDI object with [" + name + "] not found: JNDI implementation returned null");
159                                }
160                                return located;
161                        }
162                });
163        }
164
165        /**
166         * Look up the object with the given name in the current JNDI context.
167         * @param name the JNDI name of the object
168         * @param requiredType type the JNDI object must match. Can be an interface or
169         * superclass of the actual class, or {@code null} for any match. For example,
170         * if the value is {@code Object.class}, this method will succeed whatever
171         * the class of the returned instance.
172         * @return object found (cannot be {@code null}; if a not so well-behaved
173         * JNDI implementations returns null, a NamingException gets thrown)
174         * @throws NamingException if there is no object with the given
175         * name bound to JNDI
176         */
177        @SuppressWarnings("unchecked")
178        public <T> T lookup(String name, Class<T> requiredType) throws NamingException {
179                Object jndiObject = lookup(name);
180                if (requiredType != null && !requiredType.isInstance(jndiObject)) {
181                        throw new TypeMismatchNamingException(
182                                        name, requiredType, (jndiObject != null ? jndiObject.getClass() : null));
183                }
184                return (T) jndiObject;
185        }
186
187        /**
188         * Bind the given object to the current JNDI context, using the given name.
189         * @param name the JNDI name of the object
190         * @param object the object to bind
191         * @throws NamingException thrown by JNDI, mostly name already bound
192         */
193        public void bind(final String name, final Object object) throws NamingException {
194                if (logger.isDebugEnabled()) {
195                        logger.debug("Binding JNDI object with name [" + name + "]");
196                }
197                execute(new JndiCallback<Object>() {
198                        @Override
199                        public Object doInContext(Context ctx) throws NamingException {
200                                ctx.bind(name, object);
201                                return null;
202                        }
203                });
204        }
205
206        /**
207         * Rebind the given object to the current JNDI context, using the given name.
208         * Overwrites any existing binding.
209         * @param name the JNDI name of the object
210         * @param object the object to rebind
211         * @throws NamingException thrown by JNDI
212         */
213        public void rebind(final String name, final Object object) throws NamingException {
214                if (logger.isDebugEnabled()) {
215                        logger.debug("Rebinding JNDI object with name [" + name + "]");
216                }
217                execute(new JndiCallback<Object>() {
218                        @Override
219                        public Object doInContext(Context ctx) throws NamingException {
220                                ctx.rebind(name, object);
221                                return null;
222                        }
223                });
224        }
225
226        /**
227         * Remove the binding for the given name from the current JNDI context.
228         * @param name the JNDI name of the object
229         * @throws NamingException thrown by JNDI, mostly name not found
230         */
231        public void unbind(final String name) throws NamingException {
232                if (logger.isDebugEnabled()) {
233                        logger.debug("Unbinding JNDI object with name [" + name + "]");
234                }
235                execute(new JndiCallback<Object>() {
236                        @Override
237                        public Object doInContext(Context ctx) throws NamingException {
238                                ctx.unbind(name);
239                                return null;
240                        }
241                });
242        }
243
244}