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