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 log = LoggerFactory.getLogger(JndiTemplate.class);
48  
49      private Properties environment;
50  
51      /** Create a new JndiTemplate instance. */
52      public JndiTemplate() {
53      }
54  
55      /**
56       * Create a new JndiTemplate instance, using the given environment.
57       *
58       * @param environment the Properties to initialize with
59       */
60      public JndiTemplate(Properties environment) {
61          this.environment = environment;
62      }
63  
64      /**
65       * Set the environment for the JNDI InitialContext.
66       *
67       * @param environment the Properties to initialize with
68       */
69      public void setEnvironment(Properties environment) {
70          this.environment = environment;
71      }
72  
73      /**
74       * Return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
75       *
76       * @return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
77       */
78      public Properties getEnvironment() {
79          return this.environment;
80      }
81  
82      /**
83       * Execute the given JNDI context callback implementation.
84       *
85       * @param contextCallback JndiCallback implementation
86       * @return a result object returned by the callback, or <code>null</code>
87       * @throws NamingException thrown by the callback implementation
88       * @see #createInitialContext
89       */
90      public Object execute(JndiCallback contextCallback) throws NamingException {
91          Context ctx = createInitialContext();
92          try {
93              return contextCallback.doInContext(ctx);
94          }
95          finally {
96              try {
97                  ctx.close();
98              } catch (NamingException ex) {
99                  log.debug("Could not close JNDI InitialContext", ex);
100             }
101         }
102     }
103 
104     /**
105      * Create a new JNDI initial context. Invoked by {@link #execute}.
106      * <p>The default implementation use this template's environment settings.
107      * Can be subclassed for custom contexts, e.g. for testing.
108      *
109      * @return the initial Context instance
110      * @throws NamingException in case of initialization errors
111      */
112     @SuppressWarnings({"unchecked"})
113     protected Context createInitialContext() throws NamingException {
114         Properties env = getEnvironment();
115         Hashtable icEnv = null;
116         if (env != null) {
117             icEnv = new Hashtable(env.size());
118             for (Enumeration en = env.propertyNames(); en.hasMoreElements();) {
119                 String key = (String) en.nextElement();
120                 icEnv.put(key, env.getProperty(key));
121             }
122         }
123         return new InitialContext(icEnv);
124     }
125 
126     /**
127      * Look up the object with the given name in the current JNDI context.
128      *
129      * @param name the JNDI name of the object
130      * @return object found (cannot be <code>null</code>; if a not so well-behaved
131      *         JNDI implementations returns null, a NamingException gets thrown)
132      * @throws NamingException if there is no object with the given
133      *                         name bound to JNDI
134      */
135     public Object lookup(final String name) throws NamingException {
136         log.debug("Looking up JNDI object with name '{}'", name);
137         return execute(new JndiCallback() {
138             public Object doInContext(Context ctx) throws NamingException {
139                 Object located = ctx.lookup(name);
140                 if (located == null) {
141                     throw new NameNotFoundException(
142                             "JNDI object with [" + name + "] not found: JNDI implementation returned null");
143                 }
144                 return located;
145             }
146         });
147     }
148 
149     /**
150      * Look up the object with the given name in the current JNDI context.
151      *
152      * @param name         the JNDI name of the object
153      * @param requiredType type the JNDI object must match. Can be an interface or
154      *                     superclass of the actual class, or <code>null</code> for any match. For example,
155      *                     if the value is <code>Object.class</code>, this method will succeed whatever
156      *                     the class of the returned instance.
157      * @return object found (cannot be <code>null</code>; if a not so well-behaved
158      *         JNDI implementations returns null, a NamingException gets thrown)
159      * @throws NamingException if there is no object with the given
160      *                         name bound to JNDI
161      */
162     public Object lookup(String name, Class requiredType) throws NamingException {
163         Object jndiObject = lookup(name);
164         if (requiredType != null && !requiredType.isInstance(jndiObject)) {
165             String msg = "Jndi object acquired under name '" + name + "' is of type [" +
166                     jndiObject.getClass().getName() + "] and not assignable to the required type [" +
167                     requiredType.getName() + "].";
168             throw new NamingException(msg);
169         }
170         return jndiObject;
171     }
172 
173     /**
174      * Bind the given object to the current JNDI context, using the given name.
175      *
176      * @param name   the JNDI name of the object
177      * @param object the object to bind
178      * @throws NamingException thrown by JNDI, mostly name already bound
179      */
180     public void bind(final String name, final Object object) throws NamingException {
181         log.debug("Binding JNDI object with name '{}'", name);
182         execute(new JndiCallback() {
183             public Object doInContext(Context ctx) throws NamingException {
184                 ctx.bind(name, object);
185                 return null;
186             }
187         });
188     }
189 
190     /**
191      * Rebind the given object to the current JNDI context, using the given name.
192      * Overwrites any existing binding.
193      *
194      * @param name   the JNDI name of the object
195      * @param object the object to rebind
196      * @throws NamingException thrown by JNDI
197      */
198     public void rebind(final String name, final Object object) throws NamingException {
199         log.debug("Rebinding JNDI object with name '{}'", name);
200         execute(new JndiCallback() {
201             public Object doInContext(Context ctx) throws NamingException {
202                 ctx.rebind(name, object);
203                 return null;
204             }
205         });
206     }
207 
208     /**
209      * Remove the binding for the given name from the current JNDI context.
210      *
211      * @param name the JNDI name of the object
212      * @throws NamingException thrown by JNDI, mostly name not found
213      */
214     public void unbind(final String name) throws NamingException {
215         log.debug("Unbinding JNDI object with name '{}'", name);
216         execute(new JndiCallback() {
217             public Object doInContext(Context ctx) throws NamingException {
218                 ctx.unbind(name);
219                 return null;
220             }
221         });
222     }
223 
224 }