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