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     * @see JndiCallback
043     * @see #execute
044     */
045    public class JndiTemplate {
046    
047        private static final Logger log = LoggerFactory.getLogger(JndiTemplate.class);
048    
049        private Properties environment;
050    
051        /** Create a new JndiTemplate instance. */
052        public JndiTemplate() {
053        }
054    
055        /**
056         * Create a new JndiTemplate instance, using the given environment.
057         *
058         * @param environment the Properties to initialize with
059         */
060        public JndiTemplate(Properties environment) {
061            this.environment = environment;
062        }
063    
064        /**
065         * Set the environment for the JNDI InitialContext.
066         *
067         * @param environment the Properties to initialize with
068         */
069        public void setEnvironment(Properties environment) {
070            this.environment = environment;
071        }
072    
073        /**
074         * Return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
075         *
076         * @return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
077         */
078        public Properties getEnvironment() {
079            return this.environment;
080        }
081    
082        /**
083         * Execute the given JNDI context callback implementation.
084         *
085         * @param contextCallback JndiCallback implementation
086         * @return a result object returned by the callback, or <code>null</code>
087         * @throws NamingException thrown by the callback implementation
088         * @see #createInitialContext
089         */
090        public Object execute(JndiCallback contextCallback) throws NamingException {
091            Context ctx = createInitialContext();
092            try {
093                return contextCallback.doInContext(ctx);
094            }
095            finally {
096                try {
097                    ctx.close();
098                } catch (NamingException ex) {
099                    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    }