001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.jndi;
020
021 import java.util.Enumeration;
022 import java.util.Hashtable;
023 import java.util.Properties;
024 import javax.naming.Context;
025 import javax.naming.InitialContext;
026 import javax.naming.NameNotFoundException;
027 import javax.naming.NamingException;
028
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
031
032 /**
033 * Helper class that simplifies JNDI operations. It provides methods to lookup and
034 * bind objects, and allows implementations of the {@link JndiCallback} interface
035 * to perform any operation they like with a JNDI naming context provided.
036 * <p/>
037 * <p>Note that this implementation is an almost exact copy of the Spring Framework's identically named class from
038 * their 2.5.4 distribution - we didn't want to re-invent the wheel, but not require a full dependency on the
039 * Spring framework, nor does Spring make available only its JNDI classes in a small jar, or we would have used that.
040 * Since Shiro is also Apache 2.0 licensed, all regular licenses and conditions and authors have remained in tact.
041 *
042 * @author Rod Johnson
043 * @author Juergen Hoeller
044 * @see JndiCallback
045 * @see #execute
046 */
047 public class JndiTemplate {
048
049 private static final Logger log = LoggerFactory.getLogger(JndiTemplate.class);
050
051 private Properties environment;
052
053 /** Create a new JndiTemplate instance. */
054 public JndiTemplate() {
055 }
056
057 /**
058 * Create a new JndiTemplate instance, using the given environment.
059 *
060 * @param environment the Properties to initialize with
061 */
062 public JndiTemplate(Properties environment) {
063 this.environment = environment;
064 }
065
066 /**
067 * Set the environment for the JNDI InitialContext.
068 *
069 * @param environment the Properties to initialize with
070 */
071 public void setEnvironment(Properties environment) {
072 this.environment = environment;
073 }
074
075 /**
076 * Return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
077 *
078 * @return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
079 */
080 public Properties getEnvironment() {
081 return this.environment;
082 }
083
084 /**
085 * Execute the given JNDI context callback implementation.
086 *
087 * @param contextCallback JndiCallback implementation
088 * @return a result object returned by the callback, or <code>null</code>
089 * @throws NamingException thrown by the callback implementation
090 * @see #createInitialContext
091 */
092 public Object execute(JndiCallback contextCallback) throws NamingException {
093 Context ctx = createInitialContext();
094 try {
095 return contextCallback.doInContext(ctx);
096 }
097 finally {
098 try {
099 ctx.close();
100 } catch (NamingException ex) {
101 log.debug("Could not close JNDI InitialContext", ex);
102 }
103 }
104 }
105
106 /**
107 * Create a new JNDI initial context. Invoked by {@link #execute}.
108 * <p>The default implementation use this template's environment settings.
109 * Can be subclassed for custom contexts, e.g. for testing.
110 *
111 * @return the initial Context instance
112 * @throws NamingException in case of initialization errors
113 */
114 @SuppressWarnings({"unchecked"})
115 protected Context createInitialContext() throws NamingException {
116 Properties env = getEnvironment();
117 Hashtable icEnv = null;
118 if (env != null) {
119 icEnv = new Hashtable(env.size());
120 for (Enumeration en = env.propertyNames(); en.hasMoreElements();) {
121 String key = (String) en.nextElement();
122 icEnv.put(key, env.getProperty(key));
123 }
124 }
125 return new InitialContext(icEnv);
126 }
127
128 /**
129 * Look up the object with the given name in the current JNDI context.
130 *
131 * @param name the JNDI name of the object
132 * @return object found (cannot be <code>null</code>; if a not so well-behaved
133 * JNDI implementations returns null, a NamingException gets thrown)
134 * @throws NamingException if there is no object with the given
135 * name bound to JNDI
136 */
137 public Object lookup(final String name) throws NamingException {
138 log.debug("Looking up JNDI object with name '{}'", name);
139 return execute(new JndiCallback() {
140 public Object doInContext(Context ctx) throws NamingException {
141 Object located = ctx.lookup(name);
142 if (located == null) {
143 throw new NameNotFoundException(
144 "JNDI object with [" + name + "] not found: JNDI implementation returned null");
145 }
146 return located;
147 }
148 });
149 }
150
151 /**
152 * Look up the object with the given name in the current JNDI context.
153 *
154 * @param name the JNDI name of the object
155 * @param requiredType type the JNDI object must match. Can be an interface or
156 * superclass of the actual class, or <code>null</code> for any match. For example,
157 * if the value is <code>Object.class</code>, this method will succeed whatever
158 * the class of the returned instance.
159 * @return object found (cannot be <code>null</code>; if a not so well-behaved
160 * JNDI implementations returns null, a NamingException gets thrown)
161 * @throws NamingException if there is no object with the given
162 * name bound to JNDI
163 */
164 public Object lookup(String name, Class requiredType) throws NamingException {
165 Object jndiObject = lookup(name);
166 if (requiredType != null && !requiredType.isInstance(jndiObject)) {
167 String msg = "Jndi object acquired under name '" + name + "' is of type [" +
168 jndiObject.getClass().getName() + "] and not assignable to the required type [" +
169 requiredType.getName() + "].";
170 throw new NamingException(msg);
171 }
172 return jndiObject;
173 }
174
175 /**
176 * Bind the given object to the current JNDI context, using the given name.
177 *
178 * @param name the JNDI name of the object
179 * @param object the object to bind
180 * @throws NamingException thrown by JNDI, mostly name already bound
181 */
182 public void bind(final String name, final Object object) throws NamingException {
183 log.debug("Binding JNDI object with name '{}'", name);
184 execute(new JndiCallback() {
185 public Object doInContext(Context ctx) throws NamingException {
186 ctx.bind(name, object);
187 return null;
188 }
189 });
190 }
191
192 /**
193 * Rebind the given object to the current JNDI context, using the given name.
194 * Overwrites any existing binding.
195 *
196 * @param name the JNDI name of the object
197 * @param object the object to rebind
198 * @throws NamingException thrown by JNDI
199 */
200 public void rebind(final String name, final Object object) throws NamingException {
201 log.debug("Rebinding JNDI object with name '{}'", name);
202 execute(new JndiCallback() {
203 public Object doInContext(Context ctx) throws NamingException {
204 ctx.rebind(name, object);
205 return null;
206 }
207 });
208 }
209
210 /**
211 * Remove the binding for the given name from the current JNDI context.
212 *
213 * @param name the JNDI name of the object
214 * @throws NamingException thrown by JNDI, mostly name not found
215 */
216 public void unbind(final String name) throws NamingException {
217 log.debug("Unbinding JNDI object with name '{}'", name);
218 execute(new JndiCallback() {
219 public Object doInContext(Context ctx) throws NamingException {
220 ctx.unbind(name);
221 return null;
222 }
223 });
224 }
225
226 }