Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
EnvironmentLoader |
|
| 4.8;4.8 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one | |
3 | * or more contributor license agreements. See the NOTICE file | |
4 | * distributed with this work for additional information | |
5 | * regarding copyright ownership. The ASF licenses this file | |
6 | * to you under the Apache License, Version 2.0 (the | |
7 | * "License"); you may not use this file except in compliance | |
8 | * with the License. You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
18 | */ | |
19 | package org.apache.shiro.web.env; | |
20 | ||
21 | import org.apache.shiro.config.ConfigurationException; | |
22 | import org.apache.shiro.config.ResourceConfigurable; | |
23 | import org.apache.shiro.util.ClassUtils; | |
24 | import org.apache.shiro.util.LifecycleUtils; | |
25 | import org.apache.shiro.util.StringUtils; | |
26 | import org.apache.shiro.util.UnknownClassException; | |
27 | import org.slf4j.Logger; | |
28 | import org.slf4j.LoggerFactory; | |
29 | ||
30 | import javax.servlet.ServletContext; | |
31 | ||
32 | /** | |
33 | * An {@code EnvironmentLoader} is responsible for loading a web application's Shiro {@link WebEnvironment} | |
34 | * (which includes the web app's {@link org.apache.shiro.web.mgt.WebSecurityManager WebSecurityManager}) into the | |
35 | * {@code ServletContext} at application startup. | |
36 | * <p/> | |
37 | * In Shiro 1.1 and earlier, the Shiro ServletFilter was responsible for creating the {@code WebSecurityManager} and | |
38 | * any additional objects (security filters, etc). However, any component not filtered by the Shiro Filter (such | |
39 | * as other context listeners) was not able to easily acquire the these objects to perform security operations. | |
40 | * <p/> | |
41 | * Due to this, in Shiro 1.2 and later, this {@code EnvironmentLoader} (or more likely, the | |
42 | * {@link EnvironmentLoaderListener} subclass) is the preferred mechanism to initialize | |
43 | * a Shiro environment. The Shiro Filter, while still required for request filtering, will not perform this | |
44 | * initialization at startup if the {@code EnvironmentLoader} (or listener) runs first. | |
45 | * <h2>Usage</h2> | |
46 | * This implementation will look for two servlet context {@code context-param}s in {@code web.xml}: | |
47 | * {@code shiroEnvironmentClass} and {@code shiroConfigLocations} that customize how the {@code WebEnvironment} instance | |
48 | * will be initialized. | |
49 | * <h3>shiroEnvironmentClass</h3> | |
50 | * The {@code shiroEnvironmentClass} {@code context-param}, if it exists, allows you to specify the | |
51 | * fully-qualified implementation class name of the {@link WebEnvironment} to instantiate. For example: | |
52 | * <pre> | |
53 | * <context-param> | |
54 | * <param-name>shiroEnvironmentClass</param-name> | |
55 | * <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value> | |
56 | * </context-param> | |
57 | * </pre> | |
58 | * If not specified, the default value is the {@link IniWebEnvironment} class, which assumes Shiro's default | |
59 | * <a href="http://shiro.apache.org/configuration.html">INI configuration format</a> | |
60 | * <h3>shiroConfigLocations</h3> | |
61 | * The {@code shiroConfigLocations} {@code context-param}, if it exists, allows you to specify the config location(s) | |
62 | * (resource path(s)) that will be relayed to the instantiated {@link WebEnvironment}. For example: | |
63 | * <pre> | |
64 | * <context-param> | |
65 | * <param-name>shiroConfigLocations</param-name> | |
66 | * <param-value>/WEB-INF/someLocation/shiro.ini</param-value> | |
67 | * </context-param> | |
68 | * </pre> | |
69 | * The {@code WebEnvironment} implementation must implement the {@link ResourceConfigurable} interface if it is to | |
70 | * acquire the {@code shiroConfigLocations} value. | |
71 | * <p/> | |
72 | * If this {@code context-param} is not specified, the {@code WebEnvironment} instance determines default resource | |
73 | * lookup behavior. For example, the {@link IniWebEnvironment} will check the following two locations for INI config | |
74 | * by default (in order): | |
75 | * <ol> | |
76 | * <li>/WEB-INF/shiro.ini</li> | |
77 | * <li>classpath:shiro.ini</li> | |
78 | * </ol> | |
79 | * <h2>Web Security Enforcement</h2> | |
80 | * Using this loader will only initialize Shiro's environment in a web application - it will not filter web requests or | |
81 | * perform web-specific security operations. To do this, you must ensure that you have also configured the | |
82 | * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} in {@code web.xml}. | |
83 | * <p/> | |
84 | * Finally, it should be noted that this implementation was based on ideas in Spring 3's | |
85 | * {@code org.springframework.web.context.ContextLoader} implementation - no need to reinvent the wheel for this common | |
86 | * behavior. | |
87 | * | |
88 | * @see EnvironmentLoaderListener | |
89 | * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter | |
90 | * @since 1.2 | |
91 | */ | |
92 | 0 | public class EnvironmentLoader { |
93 | ||
94 | /** | |
95 | * Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use: | |
96 | * {@code shiroEnvironmentClass} | |
97 | */ | |
98 | public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass"; | |
99 | ||
100 | /** | |
101 | * Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance: | |
102 | * {@code shiroConfigLocations} | |
103 | */ | |
104 | public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations"; | |
105 | ||
106 | 1 | public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY"; |
107 | ||
108 | 1 | private static final Logger log = LoggerFactory.getLogger(EnvironmentLoader.class); |
109 | ||
110 | /** | |
111 | * Initializes Shiro's {@link WebEnvironment} instance for the specified {@code ServletContext} based on the | |
112 | * {@link #CONFIG_LOCATIONS_PARAM} value. | |
113 | * | |
114 | * @param servletContext current servlet context | |
115 | * @return the new Shiro {@code WebEnvironment} instance. | |
116 | * @throws IllegalStateException if an existing WebEnvironment has already been initialized and associated with | |
117 | * the specified {@code ServletContext}. | |
118 | */ | |
119 | public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException { | |
120 | ||
121 | 0 | if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) { |
122 | 0 | String msg = "There is already a Shiro environment associated with the current ServletContext. " + |
123 | "Check if you have multiple EnvironmentLoader* definitions in your web.xml!"; | |
124 | 0 | throw new IllegalStateException(msg); |
125 | } | |
126 | ||
127 | 0 | servletContext.log("Initializing Shiro environment"); |
128 | 0 | log.info("Starting Shiro environment initialization."); |
129 | ||
130 | 0 | long startTime = System.currentTimeMillis(); |
131 | ||
132 | try { | |
133 | 0 | WebEnvironment environment = createEnvironment(servletContext); |
134 | 0 | servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment); |
135 | ||
136 | 0 | log.debug("Published WebEnvironment as ServletContext attribute with name [{}]", |
137 | ENVIRONMENT_ATTRIBUTE_KEY); | |
138 | ||
139 | 0 | if (log.isInfoEnabled()) { |
140 | 0 | long elapsed = System.currentTimeMillis() - startTime; |
141 | 0 | log.info("Shiro environment initialized in {} ms.", elapsed); |
142 | } | |
143 | ||
144 | 0 | return environment; |
145 | 0 | } catch (RuntimeException ex) { |
146 | 0 | log.error("Shiro environment initialization failed", ex); |
147 | 0 | servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); |
148 | 0 | throw ex; |
149 | 0 | } catch (Error err) { |
150 | 0 | log.error("Shiro environment initialization failed", err); |
151 | 0 | servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err); |
152 | 0 | throw err; |
153 | } | |
154 | } | |
155 | ||
156 | /** | |
157 | * Return the WebEnvironment implementation class to use, either the default | |
158 | * {@link IniWebEnvironment} or a custom class if specified. | |
159 | * | |
160 | * @param servletContext current servlet context | |
161 | * @return the WebEnvironment implementation class to use | |
162 | * @see #ENVIRONMENT_CLASS_PARAM | |
163 | * @see IniWebEnvironment | |
164 | */ | |
165 | protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) { | |
166 | 0 | String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM); |
167 | 0 | if (className != null) { |
168 | try { | |
169 | 0 | return ClassUtils.forName(className); |
170 | 0 | } catch (UnknownClassException ex) { |
171 | 0 | throw new ConfigurationException( |
172 | "Failed to load custom WebEnvironment class [" + className + "]", ex); | |
173 | } | |
174 | } else { | |
175 | 0 | return IniWebEnvironment.class; |
176 | } | |
177 | } | |
178 | ||
179 | /** | |
180 | * Instantiates a {@link WebEnvironment} based on the specified ServletContext. | |
181 | * <p/> | |
182 | * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a | |
183 | * {@link WebEnvironment} implementation class to use. That class is instantiated, configured, and returned. | |
184 | * <p/> | |
185 | * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if | |
186 | * desired. If not specified, the default {@link IniWebEnvironment} implementation will be used. | |
187 | * | |
188 | * @param sc current servlet context | |
189 | * @return the constructed Shiro WebEnvironment instance | |
190 | * @see MutableWebEnvironment | |
191 | * @see ResourceConfigurable | |
192 | */ | |
193 | protected WebEnvironment createEnvironment(ServletContext sc) { | |
194 | ||
195 | 0 | Class<?> clazz = determineWebEnvironmentClass(sc); |
196 | 0 | if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) { |
197 | 0 | throw new ConfigurationException("Custom WebEnvironment class [" + clazz.getName() + |
198 | "] is not of required type [" + WebEnvironment.class.getName() + "]"); | |
199 | } | |
200 | ||
201 | 0 | String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM); |
202 | 0 | boolean configSpecified = StringUtils.hasText(configLocations); |
203 | ||
204 | 0 | if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) { |
205 | 0 | String msg = "WebEnvironment class [" + clazz.getName() + "] does not implement the " + |
206 | ResourceConfigurable.class.getName() + "interface. This is required to accept any " + | |
207 | "configured " + CONFIG_LOCATIONS_PARAM + "value(s)."; | |
208 | 0 | throw new ConfigurationException(msg); |
209 | } | |
210 | ||
211 | 0 | MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz); |
212 | ||
213 | 0 | environment.setServletContext(sc); |
214 | ||
215 | 0 | if (configSpecified && (environment instanceof ResourceConfigurable)) { |
216 | 0 | ((ResourceConfigurable) environment).setConfigLocations(configLocations); |
217 | } | |
218 | ||
219 | 0 | customizeEnvironment(environment); |
220 | ||
221 | 0 | LifecycleUtils.init(environment); |
222 | ||
223 | 0 | return environment; |
224 | } | |
225 | ||
226 | protected void customizeEnvironment(WebEnvironment environment) { | |
227 | 0 | } |
228 | ||
229 | /** | |
230 | * Destroys the {@link WebEnvironment} for the given servlet context. | |
231 | * | |
232 | * @param servletContext the ServletContext attributed to the WebSecurityManager | |
233 | */ | |
234 | public void destroyEnvironment(ServletContext servletContext) { | |
235 | 0 | servletContext.log("Cleaning up Shiro Environment"); |
236 | try { | |
237 | 0 | Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY); |
238 | 0 | LifecycleUtils.destroy(environment); |
239 | } finally { | |
240 | 0 | servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY); |
241 | 0 | } |
242 | 0 | } |
243 | } |