You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2011/05/18 01:27:47 UTC

svn commit: r1104630 [1/2] - in /shiro/trunk: ./ core/src/main/java/org/apache/shiro/config/ core/src/main/java/org/apache/shiro/env/ core/src/main/java/org/apache/shiro/util/ samples/web/src/main/webapp/WEB-INF/ web/src/main/java/org/apache/shiro/web/...

Author: lhazlewood
Date: Tue May 17 23:27:46 2011
New Revision: 1104630

URL: http://svn.apache.org/viewvc?rev=1104630&view=rev
Log:
SHIRO-293: implemented new 'Environment' and 'WebEnvironment' concepts, which are loaded in web apps via the new EnvironmentLoader and EnvironmentLoaderListener implementations.  The EnvironmentLoaderListener is now the preferred way to initialize Shiro in a standard web application (in web.xml), with the new org.apache.shiro.web.servlet.ServletFilter being the new preferred servlet filter in web.xml.  IniShiroFilter has been deprecated.

Added:
    shiro/trunk/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java
    shiro/trunk/core/src/main/java/org/apache/shiro/env/
    shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
    shiro/trunk/core/src/main/java/org/apache/shiro/env/Environment.java
    shiro/trunk/core/src/main/java/org/apache/shiro/env/package-info.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/package-info.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java
    shiro/trunk/web/src/test/groovy/org/apache/shiro/web/filter/
    shiro/trunk/web/src/test/groovy/org/apache/shiro/web/filter/mgt/
    shiro/trunk/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy
    shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy
Removed:
    shiro/trunk/web/src/test/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.java
Modified:
    shiro/trunk/NOTICE
    shiro/trunk/RELEASE-NOTES
    shiro/trunk/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java
    shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml
    shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/package-info.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
    shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy

Modified: shiro/trunk/NOTICE
URL: http://svn.apache.org/viewvc/shiro/trunk/NOTICE?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/NOTICE (original)
+++ shiro/trunk/NOTICE Tue May 17 23:27:46 2011
@@ -1,5 +1,5 @@
 Apache Shiro
-Copyright 2008-2010 The Apache Software Foundation
+Copyright 2008-2011 The Apache Software Foundation
 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
@@ -12,4 +12,17 @@ with continued modifications.  
 Certain parts (StringUtils etc.) of the source code for this 
 product was copied for simplicity and to reduce dependencies 
 from the source code developed by the Spring Framework Project 
-(http://www.springframework.org).
\ No newline at end of file
+(http://www.springframework.org).
+
+Shiro's BCrypt Hashing support is a modification of Damien Miller's
+jBCrypt project at http://www.mindrot.org/projects/jBCrypt/, a
+port of OpenBSD's BCrypt algorithm to Java.  Per his BSD-style
+license, the following remains in-tact:
+// Copyright (c) 2006 Damien Miller <dj...@mindrot.org>
+//
+// Permission to use, copy, modify, and distribute this software for
+// any purpose with or without fee is hereby granted, provided that
+// the above copyright notice and this permission notice appear in
+// all copies.
+Note that this attribution statement applies to Shiro's BCrypt hash
+class only, not Shiro in general.

Modified: shiro/trunk/RELEASE-NOTES
URL: http://svn.apache.org/viewvc/shiro/trunk/RELEASE-NOTES?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/RELEASE-NOTES (original)
+++ shiro/trunk/RELEASE-NOTES Tue May 17 23:27:46 2011
@@ -30,4 +30,20 @@ Backwards Incompatible Changes
   appendQueryProperties(StringBuffer targetUrl, Map model, String encodingScheme)
   method has been changed to accept a StringBuilder argument instead of a
   StringBuffer per SHIRO-191.  RedirectView is considered an internal
-  implementation support class and Shiro end-users should not be affected by this.
\ No newline at end of file
+  implementation support class and Shiro end-users should not be affected by this.
+
+###########################################################
+# 1.2.0
+###########################################################
+
+Potential Breaking Changes
+--------------------------------
+- The org.apache.shiro.web.filter.mgt.FilterChainManager class's
+  addFilter(String name, Filter filter) semantics have changed.  It now no longer
+  attempts to initialize a filter by default before adding the filter to the chain.
+  If you ever called this method, you can call the
+  addFilter(name, filter, true) method to achieve the <= 1.1 behavior.
+
+
+
+

Added: shiro/trunk/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java Tue May 17 23:27:46 2011
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+/**
+ * Interface implemented by components that can be configured by resource locations (paths).
+ *
+ * @since 1.2
+ */
+public interface ResourceConfigurable {
+
+    /**
+     * Convenience method that accepts a comma-delimited string of config locations (resource paths).
+     *
+     * @param locations comma-delimited list of config locations (resource paths).
+     */
+    void setConfigLocations(String locations);
+
+    /**
+     * Sets the configuration locations (resource paths) that will be used to configure the instance.
+     *
+     * @param locations the configuration locations (resource paths) that will be used to configure the instance.
+     */
+    void setConfigLocations(String[] locations);
+
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java Tue May 17 23:27:46 2011
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.env;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.LifecycleUtils;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Simple/default {@code Environment} implementation that stores Shiro objects as key-value pairs in a
+ * {@link java.util.Map Map} instance.  The key is the object name, the value is the object itself.
+ *
+ * @since 1.2
+ */
+public class DefaultEnvironment implements Environment, Destroyable {
+
+    /**
+     * The default name under which the application's {@code SecurityManager} instance may be acquired, equal to
+     * {@code securityManager}.
+     */
+    public static final String DEFAULT_SECURITY_MANAGER_KEY = "securityManager";
+
+    protected final Map<String, Object> objects;
+    private String securityManagerName;
+
+    /**
+     * Creates a new instance with a thread-safe {@link ConcurrentHashMap} backing map.
+     */
+    public DefaultEnvironment() {
+        this(new ConcurrentHashMap<String, Object>());
+    }
+
+    /**
+     * Creates a new instance with the specified backing map.
+     *
+     * @param seed backing map to use to maintain Shiro objects.
+     */
+    public DefaultEnvironment(Map<String, ?> seed) {
+        this.securityManagerName = DEFAULT_SECURITY_MANAGER_KEY;
+        if (seed == null) {
+            throw new IllegalArgumentException("Backing map cannot be null.");
+        }
+        //noinspection unchecked
+        this.objects = (Map<String, Object>) seed;
+    }
+
+    /**
+     * Returns the application's {@code SecurityManager} instance accessible in the backing map using the
+     * {@link #getSecurityManagerName() securityManagerName} property as the lookup key.
+     * <p/>
+     * This implementation guarantees that a non-null instance is always returned, as this is expected for
+     * Environment API end-users.  If subclasses have the need to perform the map lookup without this guarantee
+     * (for example, during initialization when the instance may not have been added to the map yet), the
+     * {@link #lookupSecurityManager()} method is provided as an alternative.
+     *
+     * @return the application's {@code SecurityManager} instance accessible in the backing map using the
+     *         {@link #getSecurityManagerName() securityManagerName} property as the lookup key.
+     */
+    public SecurityManager getSecurityManager() throws IllegalStateException {
+        SecurityManager securityManager = lookupSecurityManager();
+        if (securityManager == null) {
+            throw new IllegalStateException("No SecurityManager found in Environment.  This is an invalid " +
+                    "environment state.");
+        }
+        return securityManager;
+    }
+
+    public void setSecurityManager(SecurityManager securityManager) {
+        if (securityManager == null) {
+            throw new IllegalArgumentException("Null SecurityManager instances are not allowed.");
+        }
+        String key = getSecurityManagerName();
+        this.objects.put(key, securityManager);
+    }
+
+    /**
+     * Looks up the {@code SecurityManager} instance in the backing map without performing any non-null guarantees.
+     *
+     * @return the {@code SecurityManager} in the backing map, or {@code null} if it has not yet been populated.
+     */
+    protected SecurityManager lookupSecurityManager() {
+        String key = getSecurityManagerName();
+        return (SecurityManager) this.objects.get(key);
+    }
+
+    /**
+     * Returns the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
+     * instance.  Unless set otherwise, the default is {@code securityManager}.
+     *
+     * @return the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
+     *         instance.
+     */
+    public String getSecurityManagerName() {
+        return securityManagerName;
+    }
+
+    /**
+     * Sets the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
+     * instance.  Unless set otherwise, the default is {@code securityManager}.
+     *
+     * @param securityManagerName the name of the {@link SecurityManager} instance in the backing map.  Used as a key
+     *                            to lookup the instance. 
+     */
+    public void setSecurityManagerName(String securityManagerName) {
+        this.securityManagerName = securityManagerName;
+    }
+
+    /**
+     * Returns the live (modifiable) internal objects collection.
+     *
+     * @return the live (modifiable) internal objects collection.
+     */
+    public Map<String,Object> getObjects() {
+        return this.objects;
+    }
+
+    public void destroy() throws Exception {
+        LifecycleUtils.destroy(this.objects.values());
+    }
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/env/Environment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/env/Environment.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/env/Environment.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/env/Environment.java Tue May 17 23:27:46 2011
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.env;
+
+import org.apache.shiro.mgt.SecurityManager;
+
+/**
+ * An {@code Environment} instance encapsulates all of the objects that Shiro requires to function.  It is essentially
+ * a 'meta' object from which all Shiro components can be obtained for an application.
+ * <p/>
+ * An {@code Environment} instance is usually created as a result of parsing a Shiro configuration file.  The
+ * environment instance can be stored in any place the application deems necessary, and from it, can retrieve any
+ * of Shiro's components that might be necessary in implementing security behavior.
+ * <p/>
+ * For example, the most obvious component accessible via an {@code Environment} instance is the application's
+ * {@link #getSecurityManager() securityManager}.
+ *
+ * @since 1.2
+ */
+public interface Environment {
+
+    /**
+     * Returns the application's {@code SecurityManager} instance.
+     *
+     * @return the application's {@code SecurityManager} instance.
+     */
+    SecurityManager getSecurityManager();
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/env/package-info.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/env/package-info.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/env/package-info.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/env/package-info.java Tue May 17 23:27:46 2011
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Concepts used to represent Shiro's aggregate state in an application.  An {@link Environment} instance represents
+ * everything Shiro needs to function in an application.
+ *
+ * @see Environment
+ */
+package org.apache.shiro.env;
\ No newline at end of file

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java Tue May 17 23:27:46 2011
@@ -65,6 +65,8 @@ public abstract class LifecycleUtils {
     public static void destroy(Object o) {
         if (o instanceof Destroyable) {
             destroy((Destroyable) o);
+        } else if (o instanceof Collection) {
+            destroy((Collection)o);
         }
     }
 

Modified: shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml (original)
+++ shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml Tue May 17 23:27:46 2011
@@ -22,10 +22,13 @@
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
+    <listener>
+        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
+    </listener>
+
     <filter>
         <filter-name>ShiroFilter</filter-name>
-        <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
-        <!-- no init-param means load config from /WEB-INF/shiro.ini or classpath:shiro.ini, whichever is found first -->
+        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
     </filter>
 
     <filter-mapping>

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java Tue May 17 23:27:46 2011
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.env.DefaultEnvironment;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+
+import javax.servlet.ServletContext;
+import java.util.Map;
+
+/**
+ * Default {@link WebEnvironment} implementation based on a backing {@link Map} instance.
+ *
+ * @since 1.2
+ */
+public class DefaultWebEnvironment extends DefaultEnvironment implements MutableWebEnvironment {
+
+    private static final String DEFAULT_FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver";
+
+    private ServletContext servletContext;
+
+    public DefaultWebEnvironment() {
+        super();
+    }
+
+    public FilterChainResolver getFilterChainResolver() {
+        return (FilterChainResolver)this.objects.get(DEFAULT_FILTER_CHAIN_RESOLVER_NAME);
+    }
+
+    public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
+        this.objects.put(DEFAULT_FILTER_CHAIN_RESOLVER_NAME, filterChainResolver);
+    }
+
+    @Override
+    public SecurityManager getSecurityManager() throws IllegalStateException {
+        return getWebSecurityManager();
+    }
+
+    @Override
+    public void setSecurityManager(SecurityManager securityManager) {
+        assertWebSecurityManager(securityManager);
+        super.setSecurityManager(securityManager);
+    }
+
+    public WebSecurityManager getWebSecurityManager() {
+        SecurityManager sm = super.getSecurityManager();
+        assertWebSecurityManager(sm);
+        return (WebSecurityManager)sm;
+    }
+
+    public void setWebSecurityManager(WebSecurityManager wsm) {
+        super.setSecurityManager(wsm);
+    }
+
+    private void assertWebSecurityManager(SecurityManager sm) {
+        if (!(sm instanceof WebSecurityManager)) {
+            String msg = "SecurityManager instance must be a " + WebSecurityManager.class.getName() + " instance.";
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+
+    public void setServletContext(ServletContext servletContext) {
+        this.servletContext = servletContext;
+    }
+}

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java Tue May 17 23:27:46 2011
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.config.ResourceConfigurable;
+import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.util.UnknownClassException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+
+/**
+ * An {@code EnvironmentLoader} is responsible for loading a web application's Shiro {@link WebEnvironment}
+ * (which includes the web app's {@link org.apache.shiro.web.mgt.WebSecurityManager WebSecurityManager}) into the
+ * {@code ServletContext} at application startup.
+ * <p/>
+ * In Shiro 1.1 and earlier, the Shiro ServletFilter was responsible for creating the {@code WebSecurityManager} and
+ * any additional objects (security filters, etc).  However, any component not filtered by the Shiro Filter (such
+ * as other context listeners) was not able to easily acquire the these objects to perform security operations.
+ * <p/>
+ * Due to this, in Shiro 1.2 and later, this {@code EnvironmentLoader} (or more likely, the
+ * {@link EnvironmentLoaderListener} subclass) is the preferred mechanism to initialize
+ * a Shiro environment.  The Shiro Filter, while still required for request filtering, will not perform this
+ * initialization at startup if the {@code EnvironmentLoader} (or listener) runs first.
+ * <h2>Usage</h2>
+ * This implementation will look for two servlet context {@code context-param}s in {@code web.xml}:
+ * {@code shiroEnvironmentClass} and {@code shiroConfigLocations} that customize how the {@code WebEnvironment} instance
+ * will be initialized.
+ * <h3>shiroEnvironmentClass</h3>
+ * The {@code shiroEnvironmentClass} {@code context-param}, if it exists, allows you to specify the
+ * fully-qualified implementation class name of the {@link WebEnvironment} to instantiate.  For example:
+ * <pre>
+ * &lt;context-param&gt;
+ *     &lt;param-name&gt;shiroEnvironmentClass&lt;/param-name&gt;
+ *     &lt;param-value&gt;com.foo.bar.shiro.MyWebEnvironment&lt;/param-value&gt;
+ * &lt;/context-param&gt;
+ * </pre>
+ * If not specified, the default value is the {@link IniWebEnvironment} class, which assumes Shiro's default
+ * <a href="http://shiro.apache.org/configuration.html">INI configuration format</a>
+ * <h3>shiroConfigLocations</h3>
+ * The {@code shiroConfigLocations} {@code context-param}, if it exists, allows you to specify the config location(s)
+ * (resource path(s)) that will be relayed to the instantiated {@link WebEnvironment}.  For example:
+ * <pre>
+ * &lt;context-param&gt;
+ *     &lt;param-name&gt;shiroConfigLocations&lt;/param-name&gt;
+ *     &lt;param-value&gt;/WEB-INF/someLocation/shiro.ini&lt;/param-value&gt;
+ * &lt;/context-param&gt;
+ * </pre>
+ * The {@code WebEnvironment} implementation must implement the {@link ResourceConfigurable} interface if it is to
+ * acquire the {@code shiroConfigLocations} value.
+ * <p/>
+ * If this {@code context-param} is not specified, the {@code WebEnvironment} instance determines default resource
+ * lookup behavior.  For example, the {@link IniWebEnvironment} will check the following two locations for INI config
+ * by default (in order):
+ * <ol>
+ * <li>/WEB-INF/shiro.ini</li>
+ * <li>classpath:shiro.ini</li>
+ * </ol>
+ * <h2>Web Security Enforcement</h2>
+ * Using this loader will only initialize Shiro's environment in a web application - it will not filter web requests or
+ * perform web-specific security operations.  To do this, you must ensure that you have also configured the
+ * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} in {@code web.xml}.
+ * <p/>
+ * Finally, it should be noted that this implementation was based on ideas in Spring 3's
+ * {@code org.springframework.web.context.ContextLoader} implementation - no need to reinvent the wheel for this common
+ * behavior.
+ *
+ * @see EnvironmentLoaderListener
+ * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter
+ * @since 1.2
+ */
+public class EnvironmentLoader {
+
+    /**
+     * Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use:
+     * {@code shiroEnvironmentClass}
+     */
+    public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass";
+
+    /**
+     * Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance:
+     * {@code shiroConfigLocations}
+     */
+    public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations";
+
+    public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY";
+
+    private static final Logger log = LoggerFactory.getLogger(EnvironmentLoader.class);
+
+    /**
+     * The Shiro environment (object graph) managed by this loader.
+     */
+    private WebEnvironment environment;
+
+    /**
+     * Initializes Shiro's {@link WebEnvironment} instance for the specified {@code ServletContext} based on the
+     * {@link #CONFIG_LOCATIONS_PARAM} value.
+     *
+     * @param servletContext current servlet context
+     * @return the new Shiro {@code WebEnvironment} instance.
+     * @throws IllegalStateException if an existing WebEnvironment has already been initialized and associated with
+     *                               the specified {@code ServletContext}.
+     */
+    public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
+
+        if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) {
+            String msg = "There is already a Shiro environment associated with the current ServletContext.  " +
+                    "Check if you have multiple EnvironmentLoader* definitions in your web.xml!";
+            throw new IllegalStateException(msg);
+        }
+
+        servletContext.log("Initializing Shiro environment");
+        log.info("Starting Shiro environment initialization.");
+
+        long startTime = System.currentTimeMillis();
+
+        try {
+            this.environment = createEnvironment(servletContext);
+            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, this.environment);
+
+            log.debug("Published WebEnvironment as ServletContext attribute with name [{}]",
+                    ENVIRONMENT_ATTRIBUTE_KEY);
+
+            if (log.isInfoEnabled()) {
+                long elapsed = System.currentTimeMillis() - startTime;
+                log.info("Shiro environment initialized in {} ms.", elapsed);
+            }
+
+            return this.environment;
+        } catch (RuntimeException ex) {
+            log.error("Shiro environment initialization failed", ex);
+            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex);
+            throw ex;
+        } catch (Error err) {
+            log.error("Shiro environment initialization failed", err);
+            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err);
+            throw err;
+        }
+    }
+
+    /**
+     * Return the WebEnvironment implementation class to use, either the default
+     * {@link IniWebEnvironment} or a custom class if specified.
+     *
+     * @param servletContext current servlet context
+     * @return the WebEnvironment implementation class to use
+     * @see #ENVIRONMENT_CLASS_PARAM
+     * @see IniWebEnvironment
+     */
+    protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) {
+        String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM);
+        if (className != null) {
+            try {
+                return ClassUtils.forName(className);
+            } catch (UnknownClassException ex) {
+                throw new ConfigurationException(
+                        "Failed to load custom WebEnvironment class [" + className + "]", ex);
+            }
+        } else {
+            return IniWebEnvironment.class;
+        }
+    }
+
+    /**
+     * Instantiates a {@link WebEnvironment} based on the specified ServletContext.
+     * <p/>
+     * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a
+     * {@link WebEnvironment} implementation class to use.  That class is instantiated, configured, and returned.
+     * <p/>
+     * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if
+     * desired.  If not specified, the default {@link IniWebEnvironment} implementation will be used.
+     *
+     * @param sc current servlet context
+     * @return the constructed Shiro WebEnvironment instance
+     * @see MutableWebEnvironment
+     * @see ResourceConfigurable
+     */
+    protected WebEnvironment createEnvironment(ServletContext sc) {
+
+        Class<?> clazz = determineWebEnvironmentClass(sc);
+        if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) {
+            throw new ConfigurationException("Custom WebEnvironment class [" + clazz.getName() +
+                    "] is not of required type [" + WebEnvironment.class.getName() + "]");
+        }
+
+        String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
+        boolean configSpecified = StringUtils.hasText(configLocations);
+
+        if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) {
+            String msg = "WebEnvironment class [" + clazz.getName() + "] does not implement the " +
+                    ResourceConfigurable.class.getName() + "interface.  This is required to accept any " +
+                    "configured " + CONFIG_LOCATIONS_PARAM + "value(s).";
+            throw new ConfigurationException(msg);
+        }
+
+        MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz);
+
+        environment.setServletContext(sc);
+
+        if (configSpecified && (environment instanceof ResourceConfigurable)) {
+            ((ResourceConfigurable) environment).setConfigLocations(configLocations);
+        }
+
+        customizeEnvironment(environment);
+
+        LifecycleUtils.init(environment);
+
+        return environment;
+    }
+
+    protected void customizeEnvironment(WebEnvironment environment) {
+    }
+
+    /**
+     * Destroys the {@link WebEnvironment} for the given servlet context.
+     *
+     * @param servletContext the ServletContext attributed to the WebSecurityManager
+     */
+    public void destroyEnvironment(ServletContext servletContext) {
+        servletContext.log("Cleaning up Shiro Environment");
+        try {
+            LifecycleUtils.destroy(this.environment);
+        } finally {
+            servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY);
+        }
+    }
+}

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java Tue May 17 23:27:46 2011
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * Bootstrap listener to startup and shutdown the web application's Shiro
+ * {@link WebEnvironment} at ServletContext startup and shutdown respectively.  This class exists only to
+ * implement the {@link ServletContextListener} interface. All 'real' logic is done in the parent
+ * {@link EnvironmentLoader} class.
+ * <h2>Usage</h2>
+ * Define the following in {@code web.xml}:
+ * <pre>
+ * &lt;listener&gt;
+ *     &lt;listener-class&gt;<code>org.apache.shiro.web.env.EnvironmentLoaderListener</code>&lt;/listener-class&gt;
+ * &lt;/listener&gt;
+ * </pre>
+ * Configuration options, such as the {@code WebEnvironment} class to instantiate as well as Shiro configuration
+ * resource locations are specified as {@code ServletContext} {@code context-param}s and are documented in the
+ * {@link EnvironmentLoader} JavaDoc.
+ * <h2>Shiro Filter</h2>
+ * This listener is almost always defined in conjunction with the
+ * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} to ensure security operations for web requests.  Please
+ * see the {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} JavaDoc for more.
+ *
+ *
+ * @see EnvironmentLoader
+ * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter
+ * @since 1.2
+ */
+public class EnvironmentLoaderListener extends EnvironmentLoader implements ServletContextListener {
+
+    /**
+     * Initializes the Shiro {@code WebEnvironment} and binds it to the {@code ServletContext} at application
+     * startup for future reference.
+     *
+     * @param sce the ServletContextEvent triggered upon application startup
+     */
+    public void contextInitialized(ServletContextEvent sce) {
+        initEnvironment(sce.getServletContext());
+    }
+
+    /**
+     * Destroys any previously created/bound {@code WebEnvironment} instance created by
+     * the {@link #contextInitialized(javax.servlet.ServletContextEvent)} method.
+     *
+     * @param sce the ServletContextEvent triggered upon application shutdown
+     */
+    public void contextDestroyed(ServletContextEvent sce) {
+        destroyEnvironment(sce.getServletContext());
+    }
+}

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java Tue May 17 23:27:46 2011
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniFactorySupport;
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.Initializable;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.config.IniFilterChainResolverFactory;
+import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * {@link WebEnvironment} implementation configured by an {@link Ini} instance or {@code Ini} resource locations.
+ *
+ * @since 1.2
+ */
+public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
+
+    public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
+
+    private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class);
+
+    /**
+     * The Ini that configures this WebEnvironment instance.
+     */
+    private Ini ini;
+
+    /**
+     * Initializes this instance by resolving any potential (explicit or resource-configured) {@link Ini}
+     * configuration and calling {@link #configure() configure} for actual instance configuration.
+     */
+    public void init() {
+        Ini ini = getIni();
+
+        String[] configLocations = getConfigLocations();
+
+        if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) &&
+                configLocations != null && configLocations.length > 0) {
+            log.warn("Explicit INI instance has been provided, but configuration locations have also been " +
+                    "specified.  The {} implementation does not currently support multiple Ini config, but this may " +
+                    "be supported in the future. Only the INI instance will be used for configuration.",
+                    IniWebEnvironment.class.getName());
+        }
+
+        if (CollectionUtils.isEmpty(ini)) {
+            log.debug("Checking any specified config locations.");
+            ini = getSpecifiedIni(configLocations);
+        }
+
+        if (CollectionUtils.isEmpty(ini)) {
+            log.debug("No INI instance or config locations specified.  Trying default config locations.");
+            ini = getDefaultIni();
+        }
+
+        if (CollectionUtils.isEmpty(ini)) {
+            String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured.";
+            throw new ConfigurationException(msg);
+        }
+
+        setIni(ini);
+
+        configure();
+    }
+
+    protected void configure() {
+
+        this.objects.clear();
+
+        WebSecurityManager securityManager = createWebSecurityManager();
+        setWebSecurityManager(securityManager);
+
+        FilterChainResolver resolver = createFilterChainResolver();
+        if (resolver != null) {
+            setFilterChainResolver(resolver);
+        }
+    }
+
+    protected Ini getSpecifiedIni(String[] configLocations) throws ConfigurationException {
+
+        Ini ini = null;
+
+        if (configLocations != null && configLocations.length > 0) {
+
+            if (configLocations.length > 1) {
+                log.warn("More than one Shiro .ini config location has been specified.  Only the first will be " +
+                        "used for configuration as the {} implementation does not currently support multiple " +
+                        "files.  This may be supported in the future however.", IniWebEnvironment.class.getName());
+            }
+
+            //required, as it is user specified:
+            ini = createIni(configLocations[0], true);
+        }
+
+        return ini;
+    }
+
+    protected Ini getDefaultIni() {
+
+        Ini ini = null;
+
+        String[] configLocations = getDefaultConfigLocations();
+        if (configLocations != null) {
+            for (String location : configLocations) {
+                ini = createIni(location, false);
+                if (!CollectionUtils.isEmpty(ini)) {
+                    log.debug("Discovered non-empty INI configuration at location '{}'.  Using for configuration.",
+                            location);
+                    break;
+                }
+            }
+        }
+
+        return ini;
+    }
+
+    /**
+     * Creates an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and
+     * is not required.
+     * <p/>
+     * If the path is required and does not exist or is empty, a {@link ConfigurationException} will be thrown.
+     *
+     * @param configLocation the resource path to load into an {@code Ini} instance.
+     * @param required       if the path must exist and be converted to a non-empty {@link Ini} instance.
+     * @return an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and
+     *         is not required.
+     * @throws ConfigurationException if the path is required but results in a null or empty Ini instance.
+     */
+    protected Ini createIni(String configLocation, boolean required) throws ConfigurationException {
+
+        Ini ini = null;
+
+        if (configLocation != null) {
+            ini = convertPathToIni(configLocation, required);
+        }
+        if (required && CollectionUtils.isEmpty(ini)) {
+            String msg = "Required configuration location '" + configLocation + "' does not exist or did not " +
+                    "contain any INI configuration.";
+            throw new ConfigurationException(msg);
+        }
+
+        return ini;
+    }
+
+    protected FilterChainResolver createFilterChainResolver() {
+
+        FilterChainResolver resolver = null;
+
+        Ini ini = getIni();
+
+        if (!CollectionUtils.isEmpty(ini)) {
+            //only create a resolver if the 'filters' or 'urls' sections are defined:
+            Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
+            Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
+            if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
+                //either the urls section or the filters section was defined.  Go ahead and create the resolver:
+                IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
+                resolver = factory.getInstance();
+            }
+        }
+
+        return resolver;
+    }
+
+    protected WebSecurityManager createWebSecurityManager() {
+        WebIniSecurityManagerFactory factory;
+        Ini ini = getIni();
+        if (CollectionUtils.isEmpty(ini)) {
+            factory = new WebIniSecurityManagerFactory();
+        } else {
+            factory = new WebIniSecurityManagerFactory(ini);
+        }
+
+        Map<String, ?> beans = factory.getBeans();
+        if (!CollectionUtils.isEmpty(beans)) {
+            this.objects.putAll(beans);
+        }
+
+        // Create the security manager and check that it implements WebSecurityManager.
+        // Otherwise, it can't be used with the filter.
+        return (WebSecurityManager) factory.getInstance();
+    }
+
+    /**
+     * Returns an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}.
+     *
+     * @return an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}.
+     */
+    protected String[] getDefaultConfigLocations() {
+        return new String[]{
+                DEFAULT_WEB_INI_RESOURCE_PATH,
+                IniFactorySupport.DEFAULT_INI_RESOURCE_PATH
+        };
+    }
+
+    /**
+     * Converts the specified file path to an {@link Ini} instance.
+     * <p/>
+     * If the path does not have a resource prefix as defined by {@link org.apache.shiro.io.ResourceUtils#hasResourcePrefix(String)}, the
+     * path is expected to be resolvable by the {@code ServletContext} via
+     * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
+     *
+     * @param path     the path of the INI resource to load into an INI instance.
+     * @param required if the specified path must exist
+     * @return an INI instance populated based on the given INI resource path.
+     */
+    private Ini convertPathToIni(String path, boolean required) {
+
+        //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior
+
+        Ini ini = null;
+
+        if (StringUtils.hasText(path)) {
+            InputStream is = null;
+
+            //SHIRO-178: Check for servlet context resource and not only resource paths:
+            if (!ResourceUtils.hasResourcePrefix(path)) {
+                is = getServletContextResourceStream(path);
+            } else {
+                try {
+                    is = ResourceUtils.getInputStreamForPath(path);
+                } catch (IOException e) {
+                    if (required) {
+                        throw new ConfigurationException(e);
+                    } else {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Unable to load optional path '" + path + "'.", e);
+                        }
+                    }
+                }
+            }
+            if (is != null) {
+                ini = new Ini();
+                ini.load(is);
+            } else {
+                if (required) {
+                    throw new ConfigurationException("Unable to load resource path '" + path + "'");
+                }
+            }
+        }
+
+        return ini;
+    }
+
+    //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior
+    private InputStream getServletContextResourceStream(String path) {
+        InputStream is = null;
+
+        path = WebUtils.normalize(path);
+        ServletContext sc = getServletContext();
+        if (sc != null) {
+            is = sc.getResourceAsStream(path);
+        }
+
+        return is;
+    }
+
+    /**
+     * Returns the {@code Ini} instance reflecting this WebEnvironment's configuration.
+     *
+     * @return the {@code Ini} instance reflecting this WebEnvironment's configuration.
+     */
+    public Ini getIni() {
+        return this.ini;
+    }
+
+    /**
+     * Allows for configuration via a direct {@link Ini} instance instead of via
+     * {@link #getConfigLocations() config locations}.
+     * <p/>
+     * If the specified instance is null or empty, the fallback/default resource-based configuration will be used.
+     *
+     * @param ini the ini instance to use for creation.
+     */
+    public void setIni(Ini ini) {
+        this.ini = ini;
+    }
+}

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java Tue May 17 23:27:46 2011
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+
+import javax.servlet.ServletContext;
+
+/**
+ * A {@code WebEnvironment} that supports 'write' operations operations.  This mainly exists to shield
+ * {@code WebEnvironment} API consumers from modification operations, which are mostly only used during Shiro
+ * environment initialization.
+ *
+ * @since 1.2
+ */
+public interface MutableWebEnvironment extends WebEnvironment {
+
+    /**
+     * Sets the {@code WebEnvironment}'s {@link FilterChainResolver}.
+     *
+     * @param filterChainResolver the {@code WebEnvironment}'s {@link FilterChainResolver}.
+     */
+    void setFilterChainResolver(FilterChainResolver filterChainResolver);
+
+    /**
+     * Sets the {@link WebEnvironment}'s associated {@code ServletContext} instance.  Invoking this method merely
+     * makes the {@code ServletContext} available to the underlying instance - it does not trigger initialization
+     * behavior.
+     *
+     * @param servletContext the {@link WebEnvironment}'s associated {@code ServletContext} instance.
+     */
+    void setServletContext(ServletContext servletContext);
+
+    /**
+     * Sets the {@code WebEnvironment}'s {@link WebSecurityManager}.
+     *
+     * @param webSecurityManager the {@code WebEnvironment}'s {@link WebSecurityManager}.
+     */
+    void setWebSecurityManager(WebSecurityManager webSecurityManager);
+}

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java Tue May 17 23:27:46 2011
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.config.ResourceConfigurable;
+import org.apache.shiro.util.StringUtils;
+
+/**
+ * Abstract implementation for {@code WebEnvironment}s that can be initialized via resource paths (config files).
+ *
+ * @since 1.2
+ */
+public abstract class ResourceBasedWebEnvironment extends DefaultWebEnvironment implements ResourceConfigurable {
+
+    private String[] configLocations;
+
+    public String[] getConfigLocations() {
+        return configLocations;
+    }
+
+    public void setConfigLocations(String locations) {
+        if (!StringUtils.hasText(locations)) {
+            throw new IllegalArgumentException("Null/empty locations argument not allowed.");
+        }
+        String[] arr = StringUtils.split(locations);
+        setConfigLocations(arr);
+    }
+
+    public void setConfigLocations(String[] configLocations) {
+        this.configLocations = configLocations;
+    }
+
+}

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java Tue May 17 23:27:46 2011
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+
+import javax.servlet.ServletContext;
+
+/**
+ * A web-specific {@link Environment} instance, used in web applications.
+ *
+ * @since 1.2
+ */
+public interface WebEnvironment extends Environment {
+
+    /**
+     * Returns the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
+     * is not available.
+     *
+     * @return the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
+     *         is not available.
+     */
+    FilterChainResolver getFilterChainResolver();
+
+    /**
+     * Returns the {@code ServletContext} associated with this {@code WebEnvironment} instance.  A web application
+     * typically only has a single {@code WebEnvironment} associated with its {@code ServletContext}.
+     *
+     * @return the {@code ServletContext} associated with this {@code WebEnvironment} instance.
+     */
+    ServletContext getServletContext();
+
+    /**
+     * Returns the web application's security manager instance.
+     *
+     * @return the web application's security manager instance.
+     */
+    WebSecurityManager getWebSecurityManager();
+}

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/env/package-info.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/env/package-info.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/env/package-info.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/env/package-info.java Tue May 17 23:27:46 2011
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Web-specific {@link org.apache.shiro.env.Environment Environment} implementation and support.  The most important
+ * components are the {@link EnvironmentLoader} and {@link EnvironmentLoaderListener}, which are used in conjunction
+ * with the {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} to enable Shiro in a web application.
+ *
+ * @see EnvironmentLoaderListener
+ * @see EnvironmentLoader
+ */
+package org.apache.shiro.web.env;
\ No newline at end of file

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java Tue May 17 23:27:46 2011
@@ -110,7 +110,7 @@ public class DefaultFilterChainManager i
     }
 
     public void addFilter(String name, Filter filter) {
-        addFilter(name, filter, true);
+        addFilter(name, filter, false);
     }
 
     public void addFilter(String name, Filter filter, boolean init) {

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java Tue May 17 23:27:46 2011
@@ -83,7 +83,7 @@ public interface FilterChainManager {
      * {@link #addToChain(String, String, String) creating filter chains}.
      * <p/>
      * Calling this method is effectively the same as calling
-     * <code>{@link #addFilter(String, javax.servlet.Filter, boolean) addFilter}(name, filter, <b>true</b>);</code>
+     * <code>{@link #addFilter(String, javax.servlet.Filter, boolean) addFilter}(name, filter, <b>false</b>);</code>
      *
      * @param name   the name to assign to the filter, used to reference the filter in chain definitions
      * @param filter the filter to initialize and then add to the pool of available filters that can be used

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java Tue May 17 23:27:46 2011
@@ -78,7 +78,10 @@ public abstract class AbstractFilter ext
      */
     protected String getInitParam(String paramName) {
         FilterConfig config = getFilterConfig();
-        return StringUtils.clean(config.getInitParameter(paramName));
+        if (config != null) {
+            return StringUtils.clean(config.getInitParameter(paramName));
+        }
+        return null;
     }
 
     /**

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java Tue May 17 23:27:46 2011
@@ -37,7 +37,13 @@ import java.io.InputStream;
 import java.util.Map;
 
 /**
- * Main Servlet Filter that configures and enables all Shiro functions within a web application by using the
+ * <h1>Deprecated</h1>
+ * This filter has been deprecated as of Shiro 1.2 in favor of using the {@link ShiroFilter} in {@code web.xml} instead.
+ * See the {@link ShiroFilter} JavaDoc for usage.
+ * <p/>
+ * ======================
+ * <p/>
+ * Servlet Filter that configures and enables all Shiro functions within a web application by using the
  * <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> configuration format.
  * <p/>
  * The actual INI configuration contents are not covered here, but instead in Shiro's
@@ -95,10 +101,12 @@ import java.util.Map;
  * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and
  * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
  *
- * @since 1.0
  * @see <a href="http://shiro.apache.org/configuration.html">Apache Shiro INI Configuration</a>
  * @see <a href="http://shiro.apache.org/web.html">Apache Shiro Web Documentation</a>
+ * @since 1.0
+ * @deprecated in 1.2 in favor of using the {@link ShiroFilter}
  */
+@Deprecated
 public class IniShiroFilter extends AbstractShiroFilter {
 
     public static final String CONFIG_INIT_PARAM_NAME = "config";

Added: shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java (added)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java Tue May 17 23:27:46 2011
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.web.env.WebEnvironment;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.util.WebUtils;
+
+/**
+ * Primary Shiro Filter for web applications configuring Shiro via Servlet &lt;listener&gt; in web.xml.
+ * <p/>
+ * As of Shiro 1.2, this is Shiro's preferred filter for {@code web.xml} configuration.  It expects the presence of a
+ * Shiro {@link org.apache.shiro.web.env.WebEnvironment WebEnvironment} in the {@code ServletContext}, also
+ * configured via {@code web.xml}.
+ * <h2>Usage</h2>
+ * As this Filter expects an available {@link org.apache.shiro.web.env.WebEnvironment WebEnvironment} instance to
+ * be configured, it must be defined in {@code web.xml} with the companion
+ * {@link org.apache.shiro.web.env.EnvironmentLoaderListener EnvironmentLoaderListener}, which performs the necessary
+ * environment setup.  For example:
+ * <pre>
+ * &lt;listener&gt;
+ *     &lt;listener-class&gt;{@link org.apache.shiro.web.env.EnvironmentLoaderListener}&lt;/listener-class&gt;
+ * &lt;/listener&gt;
+ * ...
+ * &lt;filter&gt;
+ *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
+ *     &lt;filter-class&gt;org.apache.shiro.web.servlet.ShiroFilter&lt;/filter-class&gt;
+ * &lt;/filter&gt;
+ *
+ * &lt;-- Filter all web requests.  This filter mapping is typically declared
+ *     before all others to ensure any other filters are secured as well: --&gt;
+ * &lt;filter-mapping&gt;
+ *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
+ *     &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ * &lt;/filter-mapping&gt;
+ * </pre>
+ * Configuration options (configuration file paths, etc) are specified as part of the
+ * {@code EnvironmentLoaderListener} configuration.  See the
+ * {@link org.apache.shiro.web.env.EnvironmentLoader EnvironmentLoader} JavaDoc for configuration options.
+ *
+ * @see org.apache.shiro.web.env.EnvironmentLoader EnvironmentLoader
+ * @see org.apache.shiro.web.env.EnvironmentLoaderListener EnvironmentLoaderListener
+ * @see <a href="http://shiro.apache.org/web.html">Apache Shiro Web Documentation</a>
+ * @since 1.2
+ */
+public class ShiroFilter extends AbstractShiroFilter {
+
+    /**
+     * Configures this instance based on the existing {@link org.apache.shiro.web.env.WebEnvironment} instance
+     * available to the currently accessible {@link #getServletContext() servletContext}.
+     *
+     * @see org.apache.shiro.web.env.EnvironmentLoaderListener
+     * @since 1.2
+     */
+    @Override
+    public void init() throws Exception {
+        WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());
+
+        setSecurityManager(env.getWebSecurityManager());
+
+        FilterChainResolver resolver = env.getFilterChainResolver();
+        if (resolver != null) {
+            setFilterChainResolver(resolver);
+        }
+    }
+}

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/package-info.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/package-info.java?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/package-info.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/package-info.java Tue May 17 23:27:46 2011
@@ -19,6 +19,6 @@
 /**
  * Shiro-specific implementations of the Servlet API (Servlet Filters, et al).
  *
- * @see IniShiroFilter
+ * @see ShiroFilter
  */
 package org.apache.shiro.web.servlet;

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java Tue May 17 23:27:46 2011
@@ -22,10 +22,13 @@ import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.env.EnvironmentLoader;
+import org.apache.shiro.web.env.WebEnvironment;
 import org.apache.shiro.web.filter.AccessControlFilter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.servlet.ServletContext;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
@@ -135,7 +138,7 @@ public class WebUtils {
         }
         return normalize(decodeAndCleanUriString(request, uri));
     }
-    
+
     /**
      * Normalize a relative URI path that may have relative values ("/./",
      * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
@@ -159,7 +162,7 @@ public class WebUtils {
      * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
      * Tomcat trunk, r939305
      *
-     * @param path Relative path to be normalized
+     * @param path             Relative path to be normalized
      * @param replaceBackSlash Should '\\' be replaced with '/'
      * @return normalized path
      */
@@ -187,7 +190,7 @@ public class WebUtils {
             if (index < 0)
                 break;
             normalized = normalized.substring(0, index) +
-                normalized.substring(index + 1);
+                    normalized.substring(index + 1);
         }
 
         // Resolve occurrences of "/./" in the normalized path
@@ -196,7 +199,7 @@ public class WebUtils {
             if (index < 0)
                 break;
             normalized = normalized.substring(0, index) +
-                normalized.substring(index + 2);
+                    normalized.substring(index + 2);
         }
 
         // Resolve occurrences of "/../" in the normalized path
@@ -208,14 +211,14 @@ public class WebUtils {
                 return (null);  // Trying to go outside our context
             int index2 = normalized.lastIndexOf('/', index - 1);
             normalized = normalized.substring(0, index2) +
-                normalized.substring(index + 3);
+                    normalized.substring(index + 3);
         }
 
         // Return the normalized path that we have completed
         return (normalized);
 
     }
-   
+
 
     /**
      * Decode the supplied URI string and strips any extraneous portion after a ';'.
@@ -252,6 +255,77 @@ public class WebUtils {
     }
 
     /**
+     * Find the Shiro {@link WebEnvironment} for this web application, which is typically loaded via the
+     * {@link org.apache.shiro.web.env.EnvironmentLoaderListener}.
+     * <p/>
+     * This implementation rethrows an exception that happened on environment startup to differentiate between a failed
+     * environment startup and no environment at all.
+     *
+     * @param sc ServletContext to find the web application context for
+     * @return the root WebApplicationContext for this web app
+     * @throws IllegalStateException if the root WebApplicationContext could not be found
+     * @see org.apache.shiro.web.env.EnvironmentLoader#ENVIRONMENT_ATTRIBUTE_KEY
+     * @since 1.2
+     */
+    public static WebEnvironment getRequiredWebEnvironment(ServletContext sc)
+            throws IllegalStateException {
+
+        WebEnvironment we = getWebEnvironment(sc);
+        if (we == null) {
+            throw new IllegalStateException("No WebEnvironment found: no EnvironmentLoaderListener registered?");
+        }
+        return we;
+    }
+
+    /**
+     * Find the Shiro {@link WebEnvironment} for this web application, which is typically loaded via
+     * {@link org.apache.shiro.web.env.EnvironmentLoaderListener}.
+     * <p/>
+     * This implementation rethrows an exception that happened on environment startup to differentiate between a failed
+     * environment startup and no environment at all.
+     *
+     * @param sc ServletContext to find the web application context for
+     * @return the root WebApplicationContext for this web app, or <code>null</code> if none
+     * @see org.apache.shiro.web.env.EnvironmentLoader#ENVIRONMENT_ATTRIBUTE_KEY
+     * @since 1.2
+     */
+    public static WebEnvironment getWebEnvironment(ServletContext sc) {
+        return getWebEnvironment(sc, EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY);
+    }
+
+    /**
+     * Find the Shiro {@link WebEnvironment} for this web application.
+     *
+     * @param sc       ServletContext to find the web application context for
+     * @param attrName the name of the ServletContext attribute to look for
+     * @return the desired WebEnvironment for this web app, or <code>null</code> if none
+     * @since 1.2
+     */
+    public static WebEnvironment getWebEnvironment(ServletContext sc, String attrName) {
+        if (sc == null) {
+            throw new IllegalArgumentException("ServletContext argument must not be null.");
+        }
+        Object attr = sc.getAttribute(attrName);
+        if (attr == null) {
+            return null;
+        }
+        if (attr instanceof RuntimeException) {
+            throw (RuntimeException) attr;
+        }
+        if (attr instanceof Error) {
+            throw (Error) attr;
+        }
+        if (attr instanceof Exception) {
+            throw new IllegalStateException((Exception) attr);
+        }
+        if (!(attr instanceof WebEnvironment)) {
+            throw new IllegalStateException("Context attribute is not of type WebEnvironment: " + attr);
+        }
+        return (WebEnvironment) attr;
+    }
+
+
+    /**
      * Decode the given source string with a URLDecoder. The encoding will be taken
      * from the request, falling back to the default "ISO-8859-1".
      * <p>The default implementation uses <code>URLDecoder.decode(input, enc)</code>.
@@ -269,8 +343,7 @@ public class WebUtils {
         String enc = determineEncoding(request);
         try {
             return URLDecoder.decode(source, enc);
-        }
-        catch (UnsupportedEncodingException ex) {
+        } catch (UnsupportedEncodingException ex) {
             if (log.isWarnEnabled()) {
                 log.warn("Could not decode request string [" + source + "] with encoding '" + enc +
                         "': falling back to platform default encoding; exception message: " + ex.getMessage());

Added: shiro/trunk/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy?rev=1104630&view=auto
==============================================================================
--- shiro/trunk/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy (added)
+++ shiro/trunk/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy Tue May 17 23:27:46 2011
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt
+
+import javax.servlet.Filter
+import javax.servlet.FilterChain
+import javax.servlet.FilterConfig
+import javax.servlet.ServletContext
+import org.apache.shiro.config.ConfigurationException
+import org.apache.shiro.web.filter.authz.SslFilter
+import org.apache.shiro.web.servlet.ShiroFilter
+import org.junit.Before
+import org.junit.Test
+import static org.easymock.EasyMock.*
+import static org.junit.Assert.*
+
+/**
+ * Unit tests for the {@link DefaultFilterChainManager} implementation.
+ */
+class DefaultFilterChainManagerTest {
+
+    DefaultFilterChainManager manager;
+
+    @Before
+    public void setUp() {
+        this.manager = new DefaultFilterChainManager();
+    }
+
+    @Test
+    public void testNewInstanceDefaultFilters() {
+        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
+            assertNotNull(manager.getFilter(defaultFilter.name()));
+        }
+        assertFalse(manager.hasChains());
+    }
+
+    protected FilterConfig createNiceMockFilterConfig() {
+        FilterConfig mock = createNiceMock(FilterConfig.class);
+        ServletContext mockServletContext = createNiceMock(ServletContext.class);
+        expect(mock.getServletContext()).andReturn(mockServletContext);
+        return mock;
+    }
+
+    @Test
+    public void testNewInstanceWithFilterConfig() {
+        FilterConfig mock = createNiceMockFilterConfig();
+        replay(mock);
+        this.manager = new DefaultFilterChainManager(mock);
+        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
+            assertNotNull(manager.getFilter(defaultFilter.name()));
+        }
+        assertFalse(manager.hasChains());
+        verify(mock);
+    }
+
+    @Test
+    public void testCreateChain() {
+        try {
+            manager.createChain(null, null);
+        } catch (NullPointerException expected) {
+        }
+        try {
+            manager.createChain("test", null);
+        } catch (NullPointerException expected) {
+        }
+
+        manager.createChain("test", "authc, roles[manager], perms[\"user:read,write:12345\"");
+
+        assertTrue(manager.hasChains());
+
+        Set<String> chainNames = manager.getChainNames();
+        assertNotNull(chainNames);
+        assertEquals(1, chainNames.size());
+        assertTrue(chainNames.contains("test"));
+
+        Map<String, NamedFilterList> chains = manager.getFilterChains();
+        assertEquals(1, chains.size());
+        assertTrue(chains.containsKey("test"));
+        manager.setFilterChains(chains);
+
+        NamedFilterList chain = manager.getChain("test");
+        assertNotNull(chain);
+
+        Filter filter = chain.get(0);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.authc.getFilterClass(), filter.getClass());
+
+        filter = chain.get(1);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.roles.getFilterClass(), filter.getClass());
+
+        filter = chain.get(2);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.perms.getFilterClass(), filter.getClass());
+    }
+
+    @Test
+    public void testBeanMethods() {
+        Map<String, Filter> filters = manager.getFilters();
+        assertEquals(filters.size(), DefaultFilter.values().length);
+        manager.setFilters(filters);
+    }
+
+    @Test
+    public void testAddFilter() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        replay(mockFilterConfig);
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+        manager.addFilter("test", new SslFilter());
+        Filter filter = manager.getFilter("test");
+        assertNotNull(filter);
+        assertEquals(SslFilter.class, filter.getClass());
+        verify(mockFilterConfig);
+    }
+
+    @Test
+    public void testAddFilterNoInit() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        Filter mockFilter = createNiceMock(Filter.class);
+
+        replay mockFilterConfig, mockFilter
+
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+
+        this.manager.addFilter("blah", mockFilter);
+
+        assertNotNull this.manager.filters['blah']
+        assertSame this.manager.filters['blah'], mockFilter
+
+        verify mockFilterConfig, mockFilter
+    }
+
+    public void testAddFilterNoFilterConfig() {
+        SslFilter filter = new SslFilter();
+        manager.addFilter("test", filter);
+        assertNotNull manager.filters['test']
+        assertSame manager.filters['test'], filter
+    }
+
+    @Test
+    public void testAddToChain() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        replay(mockFilterConfig);
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+
+        manager.addFilter("testSsl", new SslFilter());
+        manager.createChain("test", "anon");
+
+        try {
+            manager.addToChain("test", null);
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            manager.addToChain(null, "testSsl");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testAddToChainNotPathProcessor() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        replay(mockFilterConfig);
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+
+        manager.addFilter("nonPathProcessor", new ShiroFilter());
+        manager.createChain("test", "nonPathProcessor");
+
+        try {
+            manager.addToChain("test", "nonPathProcessor", "dummyConfig");
+        } catch (ConfigurationException expected) {
+        }
+    }
+
+    @Test
+    public void testProxy() {
+        FilterChain mock = createNiceMock(FilterChain.class);
+        replay(mock);
+        manager.createChain("test", "anon");
+        this.manager.proxy(mock, "test");
+        verify(mock);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testProxyNoChain() {
+        FilterChain mock = createNiceMock(FilterChain.class);
+        replay(mock);
+        this.manager.proxy(mock, "blah");
+        verify(mock);
+    }
+
+}

Modified: shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy?rev=1104630&r1=1104629&r2=1104630&view=diff
==============================================================================
--- shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy (original)
+++ shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy Tue May 17 23:27:46 2011
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 package org.apache.shiro.web.servlet
 
 import javax.servlet.FilterConfig