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.realm.jndi;
020    
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.Collection;
024    import java.util.List;
025    
026    import org.apache.shiro.jndi.JndiLocator;
027    import org.apache.shiro.realm.Realm;
028    import org.apache.shiro.realm.RealmFactory;
029    import org.apache.shiro.util.StringUtils;
030    
031    
032    /**
033     * Looks up one or more Realm instances from JNDI using specified {@link #setJndiNames jndiNames}.
034     *
035     * <p>This is primarily provided to support Realm instances configured in JEE and EJB environments, but will
036     * work in any environment where {@link Realm Realm} instances are bound in JNDI instead of using
037     * programmatic or text-based configuration.
038     *
039     * @author Les Hazlewood
040     * @since 0.9
041     */
042    public class JndiRealmFactory extends JndiLocator implements RealmFactory {
043    
044        Collection<String> jndiNames = null;
045    
046        /**
047         * Returns the JNDI names that will be used to look up Realm(s) from JNDI.
048         *
049         * @return the JNDI names that will be used to look up Realm(s) from JNDI.
050         * @see #setJndiNames(String)
051         * @see #setJndiNames(Collection)
052         */
053        public Collection<String> getJndiNames() {
054            return jndiNames;
055        }
056    
057        /**
058         * Sets the JNDI names that will be used to look up Realm(s) from JNDI.
059         * <p/>
060         * The order of the collection determines the order that the Realms will be returned to the SecurityManager.
061         * <p/>
062         * If you find it easier to specify these names as a comma-delmited string, you may use the
063         * {@link #setJndiNames(String)} method instead.
064         *
065         * @param jndiNames the JNDI names that will be used to look up Realm(s) from JNDI.
066         * @see #setJndiNames(String)
067         */
068        public void setJndiNames(Collection<String> jndiNames) {
069            this.jndiNames = jndiNames;
070        }
071    
072        /**
073         * Specifies a comma-delimited list of JNDI names to lookup, each one corresponding to a jndi-bound
074         * {@link Realm Realm}.  The Realms will be made available to the SecurityManager in the order
075         * they are defined.
076         *
077         * @param commaDelimited a comma-delimited list of JNDI names, each representing the JNDI name used to
078         *                       look up a corresponding jndi-bound Realm.
079         * @throws IllegalStateException if the specified argument is null or the empty string.
080         */
081        public void setJndiNames(String commaDelimited) throws IllegalStateException {
082            String arg = StringUtils.clean(commaDelimited);
083            if (arg == null) {
084                String msg = "One or more comma-delimited jndi names must be specified for the " +
085                        getClass().getName() + " to locate Realms.";
086                throw new IllegalStateException(msg);
087            }
088            String[] names = StringUtils.tokenizeToStringArray(arg, ",");
089            setJndiNames(Arrays.asList(names));
090        }
091    
092        /**
093         * Performs the JNDI lookups for each specified {@link #getJndiNames() JNDI name} and returns all
094         * discovered Realms in an ordered collection.
095         *
096         * <p>The returned Collection is in the same order as the specified
097         * {@link #setJndiNames(java.util.Collection) jndiNames}
098         *
099         * @return an ordered collection of the {@link #setJndiNames(java.util.Collection) specified Realms} found in JNDI.
100         * @throws IllegalStateException if any of the JNDI names fails to successfully look up a Realm instance.
101         */
102        public Collection<Realm> getRealms() throws IllegalStateException {
103            Collection<String> jndiNames = getJndiNames();
104            if (jndiNames == null || jndiNames.isEmpty()) {
105                String msg = "One or more jndi names must be specified for the " +
106                        getClass().getName() + " to locate Realms.";
107                throw new IllegalStateException(msg);
108            }
109            List<Realm> realms = new ArrayList<Realm>(jndiNames.size());
110            for (String name : jndiNames) {
111                try {
112                    Realm realm = (Realm) lookup(name, Realm.class);
113                    realms.add(realm);
114                } catch (Exception e) {
115                    throw new IllegalStateException("Unable to look up realm with jndi name '" + name + "'.", e);
116                }
117            }
118            return realms.isEmpty() ? null : realms;
119        }
120    }