View Javadoc
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 }