1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.shiro.jndi;
20
21 import java.util.Enumeration;
22 import java.util.Hashtable;
23 import java.util.Properties;
24 import javax.naming.Context;
25 import javax.naming.InitialContext;
26 import javax.naming.NameNotFoundException;
27 import javax.naming.NamingException;
28
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33 * Helper class that simplifies JNDI operations. It provides methods to lookup and
34 * bind objects, and allows implementations of the {@link JndiCallback} interface
35 * to perform any operation they like with a JNDI naming context provided.
36 * <p/>
37 * <p>Note that this implementation is an almost exact copy of the Spring Framework's identically named class from
38 * their 2.5.4 distribution - we didn't want to re-invent the wheel, but not require a full dependency on the
39 * Spring framework, nor does Spring make available only its JNDI classes in a small jar, or we would have used that.
40 * Since Shiro is also Apache 2.0 licensed, all regular licenses and conditions and authors have remained in tact.
41 *
42 * @see JndiCallback
43 * @see #execute
44 */
45 public class JndiTemplate {
46
47 private static final Logger LOGGER = LoggerFactory.getLogger(JndiTemplate.class);
48
49 private Properties environment;
50
51 /**
52 * Create a new JndiTemplate instance.
53 */
54 public JndiTemplate() {
55 }
56
57 /**
58 * Create a new JndiTemplate instance, using the given environment.
59 *
60 * @param environment the Properties to initialize with
61 */
62 public JndiTemplate(Properties environment) {
63 this.environment = environment;
64 }
65
66 /**
67 * Set the environment for the JNDI InitialContext.
68 *
69 * @param environment the Properties to initialize with
70 */
71 public void setEnvironment(Properties environment) {
72 this.environment = environment;
73 }
74
75 /**
76 * Return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
77 *
78 * @return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
79 */
80 public Properties getEnvironment() {
81 return this.environment;
82 }
83
84 /**
85 * Execute the given JNDI context callback implementation.
86 *
87 * @param contextCallback JndiCallback implementation
88 * @return a result object returned by the callback, or <code>null</code>
89 * @throws NamingException thrown by the callback implementation
90 * @see #createInitialContext
91 */
92 public Object execute(JndiCallback contextCallback) throws NamingException {
93 Context ctx = createInitialContext();
94 try {
95 return contextCallback.doInContext(ctx);
96 } finally {
97 try {
98 ctx.close();
99 } catch (NamingException ex) {
100 LOGGER.debug("Could not close JNDI InitialContext", ex);
101 }
102 }
103 }
104
105 /**
106 * Create a new JNDI initial context. Invoked by {@link #execute}.
107 * <p>The default implementation use this template's environment settings.
108 * Can be subclassed for custom contexts, e.g. for testing.
109 *
110 * @return the initial Context instance
111 * @throws NamingException in case of initialization errors
112 */
113 @SuppressWarnings({"unchecked"})
114 protected Context createInitialContext() throws NamingException {
115 Properties env = getEnvironment();
116 Hashtable icEnv = null;
117 if (env != null) {
118 icEnv = new Hashtable(env.size());
119 for (Enumeration en = env.propertyNames(); en.hasMoreElements(); ) {
120 String key = (String) en.nextElement();
121 icEnv.put(key, env.getProperty(key));
122 }
123 }
124 return new InitialContext(icEnv);
125 }
126
127 /**
128 * Look up the object with the given name in the current JNDI context.
129 *
130 * @param name the JNDI name of the object
131 * @return object found (cannot be <code>null</code>; if a not so well-behaved
132 * JNDI implementations returns null, a NamingException gets thrown)
133 * @throws NamingException if there is no object with the given
134 * name bound to JNDI
135 */
136 public Object lookup(final String name) throws NamingException {
137 LOGGER.debug("Looking up JNDI object with name '{}'", name);
138 return execute(new JndiCallback() {
139 public Object doInContext(Context ctx) throws NamingException {
140 Object located = ctx.lookup(name);
141 if (located == null) {
142 throw new NameNotFoundException(
143 "JNDI object with [" + name + "] not found: JNDI implementation returned null");
144 }
145 return located;
146 }
147 });
148 }
149
150 /**
151 * Look up the object with the given name in the current JNDI context.
152 *
153 * @param name the JNDI name of the object
154 * @param requiredType type the JNDI object must match. Can be an interface or
155 * superclass of the actual class, or <code>null</code> for any match. For example,
156 * if the value is <code>Object.class</code>, this method will succeed whatever
157 * the class of the returned instance.
158 * @return object found (cannot be <code>null</code>; if a not so well-behaved
159 * JNDI implementations returns null, a NamingException gets thrown)
160 * @throws NamingException if there is no object with the given
161 * name bound to JNDI
162 */
163 public Object lookup(String name, Class requiredType) throws NamingException {
164 Object jndiObject = lookup(name);
165 if (requiredType != null && !requiredType.isInstance(jndiObject)) {
166 String msg = "Jndi object acquired under name '" + name + "' is of type ["
167 + jndiObject.getClass().getName() + "] and not assignable to the required type ["
168 + requiredType.getName() + "].";
169 throw new NamingException(msg);
170 }
171 return jndiObject;
172 }
173
174 /**
175 * Bind the given object to the current JNDI context, using the given name.
176 *
177 * @param name the JNDI name of the object
178 * @param object the object to bind
179 * @throws NamingException thrown by JNDI, mostly name already bound
180 */
181 public void bind(final String name, final Object object) throws NamingException {
182 LOGGER.debug("Binding JNDI object with name '{}'", name);
183 execute(new JndiCallback() {
184 public Object doInContext(Context ctx) throws NamingException {
185 ctx.bind(name, object);
186 return null;
187 }
188 });
189 }
190
191 /**
192 * Rebind the given object to the current JNDI context, using the given name.
193 * Overwrites any existing binding.
194 *
195 * @param name the JNDI name of the object
196 * @param object the object to rebind
197 * @throws NamingException thrown by JNDI
198 */
199 public void rebind(final String name, final Object object) throws NamingException {
200 LOGGER.debug("Rebinding JNDI object with name '{}'", name);
201 execute(new JndiCallback() {
202 public Object doInContext(Context ctx) throws NamingException {
203 ctx.rebind(name, object);
204 return null;
205 }
206 });
207 }
208
209 /**
210 * Remove the binding for the given name from the current JNDI context.
211 *
212 * @param name the JNDI name of the object
213 * @throws NamingException thrown by JNDI, mostly name not found
214 */
215 public void unbind(final String name) throws NamingException {
216 LOGGER.debug("Unbinding JNDI object with name '{}'", name);
217 execute(new JndiCallback() {
218 public Object doInContext(Context ctx) throws NamingException {
219 ctx.unbind(name);
220 return null;
221 }
222 });
223 }
224
225 }