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    }