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.Properties;
022    import javax.naming.NamingException;
023    
024    import org.slf4j.Logger;
025    import org.slf4j.LoggerFactory;
026    
027    /**
028     * Convenient superclass for JNDI accessors, providing "jndiTemplate"
029     * and "jndiEnvironment" bean properties.
030     *
031     * <p>Note that this implementation is an almost exact combined copy of the Spring Framework's 'JndiAccessor' and
032     * 'JndiLocatorSupport' classes from their 2.5.4 distribution - we didn't want to re-invent the wheel, but not require
033     * a full dependency on the Spring framework, nor does Spring make available only its JNDI classes in a small jar, or
034     * we would have used that. Since Shiro is also Apache 2.0 licensed, all regular licenses and conditions and
035     * authors have remained in tact.
036     *
037     * @author Juergen Hoeller
038     * @see #setJndiTemplate
039     * @see #setJndiEnvironment
040     * @see #setResourceRef
041     * @since 1.1
042     */
043    public class JndiLocator {
044    
045        /**
046         * Private class log.
047         */
048        private static final Logger log = LoggerFactory.getLogger(JndiLocator.class);
049    
050        /**
051         * JNDI prefix used in a J2EE container
052         */
053        public static final String CONTAINER_PREFIX = "java:comp/env/";
054    
055        private boolean resourceRef = false;
056    
057        private JndiTemplate jndiTemplate = new JndiTemplate();
058    
059    
060        /**
061         * Set the JNDI template to use for JNDI lookups.
062         * <p>You can also specify JNDI environment settings via "jndiEnvironment".
063         *
064         * @see #setJndiEnvironment
065         */
066        public void setJndiTemplate(JndiTemplate jndiTemplate) {
067            this.jndiTemplate = (jndiTemplate != null ? jndiTemplate : new JndiTemplate());
068        }
069    
070        /**
071         * Return the JNDI template to use for JNDI lookups.
072         */
073        public JndiTemplate getJndiTemplate() {
074            return this.jndiTemplate;
075        }
076    
077        /**
078         * Set the JNDI environment to use for JNDI lookups.
079         * <p>Creates a JndiTemplate with the given environment settings.
080         *
081         * @see #setJndiTemplate
082         */
083        public void setJndiEnvironment(Properties jndiEnvironment) {
084            this.jndiTemplate = new JndiTemplate(jndiEnvironment);
085        }
086    
087        /**
088         * Return the JNDI environment to use for JNDI lookups.
089         */
090        public Properties getJndiEnvironment() {
091            return this.jndiTemplate.getEnvironment();
092        }
093    
094        /**
095         * Set whether the lookup occurs in a J2EE container, i.e. if the prefix
096         * "java:comp/env/" needs to be added if the JNDI name doesn't already
097         * contain it. Default is "false".
098         * <p>Note: Will only get applied if no other scheme (e.g. "java:") is given.
099         */
100        public void setResourceRef(boolean resourceRef) {
101            this.resourceRef = resourceRef;
102        }
103    
104        /**
105         * Return whether the lookup occurs in a J2EE container.
106         */
107        public boolean isResourceRef() {
108            return this.resourceRef;
109        }
110    
111    
112        /**
113         * Perform an actual JNDI lookup for the given name via the JndiTemplate.
114         * <p>If the name doesn't begin with "java:comp/env/", this prefix is added
115         * if "resourceRef" is set to "true".
116         *
117         * @param jndiName the JNDI name to look up
118         * @return the obtained object
119         * @throws javax.naming.NamingException if the JNDI lookup failed
120         * @see #setResourceRef
121         */
122        protected Object lookup(String jndiName) throws NamingException {
123            return lookup(jndiName, null);
124        }
125    
126        /**
127         * Perform an actual JNDI lookup for the given name via the JndiTemplate.
128         * <p>If the name doesn't begin with "java:comp/env/", this prefix is added
129         * if "resourceRef" is set to "true".
130         *
131         * @param jndiName     the JNDI name to look up
132         * @param requiredType the required type of the object
133         * @return the obtained object
134         * @throws NamingException if the JNDI lookup failed
135         * @see #setResourceRef
136         */
137        protected Object lookup(String jndiName, Class requiredType) throws NamingException {
138            if (jndiName == null) {
139                throw new IllegalArgumentException("jndiName argument must not be null");
140            }
141            String convertedName = convertJndiName(jndiName);
142            Object jndiObject;
143            try {
144                jndiObject = getJndiTemplate().lookup(convertedName, requiredType);
145            }
146            catch (NamingException ex) {
147                if (!convertedName.equals(jndiName)) {
148                    // Try fallback to originally specified name...
149                    if (log.isDebugEnabled()) {
150                        log.debug("Converted JNDI name [" + convertedName +
151                                "] not found - trying original name [" + jndiName + "]. " + ex);
152                    }
153                    jndiObject = getJndiTemplate().lookup(jndiName, requiredType);
154                } else {
155                    throw ex;
156                }
157            }
158            log.debug("Located object with JNDI name '{}'", convertedName);
159            return jndiObject;
160        }
161    
162        /**
163         * Convert the given JNDI name into the actual JNDI name to use.
164         * <p>The default implementation applies the "java:comp/env/" prefix if
165         * "resourceRef" is "true" and no other scheme (e.g. "java:") is given.
166         *
167         * @param jndiName the original JNDI name
168         * @return the JNDI name to use
169         * @see #CONTAINER_PREFIX
170         * @see #setResourceRef
171         */
172        protected String convertJndiName(String jndiName) {
173            // Prepend container prefix if not already specified and no other scheme given.
174            if (isResourceRef() && !jndiName.startsWith(CONTAINER_PREFIX) && jndiName.indexOf(':') == -1) {
175                jndiName = CONTAINER_PREFIX + jndiName;
176            }
177            return jndiName;
178        }
179    
180    }