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 */
019package org.apache.shiro.env;
020
021import org.apache.shiro.mgt.SecurityManager;
022import org.apache.shiro.util.Destroyable;
023import org.apache.shiro.util.LifecycleUtils;
024
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028/**
029 * Simple/default {@code Environment} implementation that stores Shiro objects as key-value pairs in a
030 * {@link java.util.Map Map} instance.  The key is the object name, the value is the object itself.
031 *
032 * @since 1.2
033 */
034public class DefaultEnvironment implements NamedObjectEnvironment, Destroyable {
035
036    /**
037     * The default name under which the application's {@code SecurityManager} instance may be acquired, equal to
038     * {@code securityManager}.
039     */
040    public static final String DEFAULT_SECURITY_MANAGER_KEY = "securityManager";
041
042    protected final Map<String, Object> objects;
043    private String securityManagerName;
044
045    /**
046     * Creates a new instance with a thread-safe {@link ConcurrentHashMap} backing map.
047     */
048    public DefaultEnvironment() {
049        this(new ConcurrentHashMap<String, Object>());
050    }
051
052    /**
053     * Creates a new instance with the specified backing map.
054     *
055     * @param seed backing map to use to maintain Shiro objects.
056     */
057    @SuppressWarnings({"unchecked"})
058    public DefaultEnvironment(Map<String, ?> seed) {
059        this.securityManagerName = DEFAULT_SECURITY_MANAGER_KEY;
060        if (seed == null) {
061            throw new IllegalArgumentException("Backing map cannot be null.");
062        }
063        this.objects = (Map<String, Object>) seed;
064    }
065
066    /**
067     * Returns the application's {@code SecurityManager} instance accessible in the backing map using the
068     * {@link #getSecurityManagerName() securityManagerName} property as the lookup key.
069     * <p/>
070     * This implementation guarantees that a non-null instance is always returned, as this is expected for
071     * Environment API end-users.  If subclasses have the need to perform the map lookup without this guarantee
072     * (for example, during initialization when the instance may not have been added to the map yet), the
073     * {@link #lookupSecurityManager()} method is provided as an alternative.
074     *
075     * @return the application's {@code SecurityManager} instance accessible in the backing map using the
076     *         {@link #getSecurityManagerName() securityManagerName} property as the lookup key.
077     */
078    public SecurityManager getSecurityManager() throws IllegalStateException {
079        SecurityManager securityManager = lookupSecurityManager();
080        if (securityManager == null) {
081            throw new IllegalStateException("No SecurityManager found in Environment.  This is an invalid " +
082                    "environment state.");
083        }
084        return securityManager;
085    }
086
087    public void setSecurityManager(SecurityManager securityManager) {
088        if (securityManager == null) {
089            throw new IllegalArgumentException("Null SecurityManager instances are not allowed.");
090        }
091        String name = getSecurityManagerName();
092        setObject(name, securityManager);
093    }
094
095    /**
096     * Looks up the {@code SecurityManager} instance in the backing map without performing any non-null guarantees.
097     *
098     * @return the {@code SecurityManager} in the backing map, or {@code null} if it has not yet been populated.
099     */
100    protected SecurityManager lookupSecurityManager() {
101        String name = getSecurityManagerName();
102        return getObject(name, SecurityManager.class);
103    }
104
105    /**
106     * Returns the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
107     * instance.  Unless set otherwise, the default is {@code securityManager}.
108     *
109     * @return the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
110     *         instance.
111     */
112    public String getSecurityManagerName() {
113        return securityManagerName;
114    }
115
116    /**
117     * Sets the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
118     * instance.  Unless set otherwise, the default is {@code securityManager}.
119     *
120     * @param securityManagerName the name of the {@link SecurityManager} instance in the backing map.  Used as a key
121     *                            to lookup the instance. 
122     */
123    public void setSecurityManagerName(String securityManagerName) {
124        this.securityManagerName = securityManagerName;
125    }
126
127    /**
128     * Returns the live (modifiable) internal objects collection.
129     *
130     * @return the live (modifiable) internal objects collection.
131     */
132    public Map<String,Object> getObjects() {
133        return this.objects;
134    }
135
136    @SuppressWarnings({"unchecked"})
137    public <T> T getObject(String name, Class<T> requiredType) throws RequiredTypeException {
138        if (name == null) {
139            throw new NullPointerException("name parameter cannot be null.");
140        }
141        if (requiredType == null) {
142            throw new NullPointerException("requiredType parameter cannot be null.");
143        }
144        Object o = this.objects.get(name);
145        if (o == null) {
146            return null;
147        }
148        if (!requiredType.isInstance(o)) {
149            String msg = "Object named '" + name + "' (of type [" + o.getClass().getName() + "]) is not of required type [" + requiredType.getName() + "].";
150            throw new RequiredTypeException(msg);
151        }
152        return (T)o;
153    }
154
155    public void setObject(String name, Object instance) {
156        if (name == null) {
157            throw new NullPointerException("name parameter cannot be null.");
158        }
159        if (instance == null) {
160            this.objects.remove(name);
161        } else {
162            this.objects.put(name, instance);
163        }
164    }
165
166
167    public void destroy() throws Exception {
168        LifecycleUtils.destroy(this.objects.values());
169    }
170}