View Javadoc
1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.servlet;
18  
19  import java.util.Enumeration;
20  import java.util.HashSet;
21  import java.util.Set;
22  import javax.servlet.ServletConfig;
23  import javax.servlet.ServletContext;
24  import javax.servlet.ServletException;
25  import javax.servlet.http.HttpServlet;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import org.springframework.beans.BeanWrapper;
31  import org.springframework.beans.BeansException;
32  import org.springframework.beans.MutablePropertyValues;
33  import org.springframework.beans.PropertyAccessorFactory;
34  import org.springframework.beans.PropertyValue;
35  import org.springframework.beans.PropertyValues;
36  import org.springframework.context.EnvironmentAware;
37  import org.springframework.core.env.ConfigurableEnvironment;
38  import org.springframework.core.env.Environment;
39  import org.springframework.core.env.EnvironmentCapable;
40  import org.springframework.core.io.Resource;
41  import org.springframework.core.io.ResourceEditor;
42  import org.springframework.core.io.ResourceLoader;
43  import org.springframework.util.Assert;
44  import org.springframework.util.StringUtils;
45  import org.springframework.web.context.support.ServletContextResourceLoader;
46  import org.springframework.web.context.support.StandardServletEnvironment;
47  
48  /**
49   * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
50   * its config parameters ({@code init-param} entries within the
51   * {@code servlet} tag in {@code web.xml}) as bean properties.
52   *
53   * <p>A handy superclass for any type of servlet. Type conversion of config
54   * parameters is automatic, with the corresponding setter method getting
55   * invoked with the converted value. It is also possible for subclasses to
56   * specify required properties. Parameters without matching bean property
57   * setter will simply be ignored.
58   *
59   * <p>This servlet leaves request handling to subclasses, inheriting the default
60   * behavior of HttpServlet ({@code doGet}, {@code doPost}, etc).
61   *
62   * <p>This generic servlet base class has no dependency on the Spring
63   * {@link org.springframework.context.ApplicationContext} concept. Simple
64   * servlets usually don't load their own context but rather access service
65   * beans from the Spring root application context, accessible via the
66   * filter's {@link #getServletContext() ServletContext} (see
67   * {@link org.springframework.web.context.support.WebApplicationContextUtils}).
68   *
69   * <p>The {@link FrameworkServlet} class is a more specific servlet base
70   * class which loads its own application context. FrameworkServlet serves
71   * as direct base class of Spring's full-fledged {@link DispatcherServlet}.
72   *
73   * @author Rod Johnson
74   * @author Juergen Hoeller
75   * @see #addRequiredProperty
76   * @see #initServletBean
77   * @see #doGet
78   * @see #doPost
79   */
80  @SuppressWarnings("serial")
81  public abstract class HttpServletBean extends HttpServlet
82  		implements EnvironmentCapable, EnvironmentAware {
83  
84  	/** Logger available to subclasses */
85  	protected final Log logger = LogFactory.getLog(getClass());
86  
87  	/**
88  	 * Set of required properties (Strings) that must be supplied as
89  	 * config parameters to this servlet.
90  	 */
91  	private final Set<String> requiredProperties = new HashSet<String>();
92  
93  	private ConfigurableEnvironment environment;
94  
95  
96  	/**
97  	 * Subclasses can invoke this method to specify that this property
98  	 * (which must match a JavaBean property they expose) is mandatory,
99  	 * and must be supplied as a config parameter. This should be called
100 	 * from the constructor of a subclass.
101 	 * <p>This method is only relevant in case of traditional initialization
102 	 * driven by a ServletConfig instance.
103 	 * @param property name of the required property
104 	 */
105 	protected final void addRequiredProperty(String property) {
106 		this.requiredProperties.add(property);
107 	}
108 
109 	/**
110 	 * Map config parameters onto bean properties of this servlet, and
111 	 * invoke subclass initialization.
112 	 * @throws ServletException if bean properties are invalid (or required
113 	 * properties are missing), or if subclass initialization fails.
114 	 */
115 	@Override
116 	public final void init() throws ServletException {
117 		if (logger.isDebugEnabled()) {
118 			logger.debug("Initializing servlet '" + getServletName() + "'");
119 		}
120 
121 		// Set bean properties from init parameters.
122 		try {
123 			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
124 			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
125 			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
126 			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
127 			initBeanWrapper(bw);
128 			bw.setPropertyValues(pvs, true);
129 		}
130 		catch (BeansException ex) {
131 			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
132 			throw ex;
133 		}
134 
135 		// Let subclasses do whatever initialization they like.
136 		initServletBean();
137 
138 		if (logger.isDebugEnabled()) {
139 			logger.debug("Servlet '" + getServletName() + "' configured successfully");
140 		}
141 	}
142 
143 	/**
144 	 * Initialize the BeanWrapper for this HttpServletBean,
145 	 * possibly with custom editors.
146 	 * <p>This default implementation is empty.
147 	 * @param bw the BeanWrapper to initialize
148 	 * @throws BeansException if thrown by BeanWrapper methods
149 	 * @see org.springframework.beans.BeanWrapper#registerCustomEditor
150 	 */
151 	protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
152 	}
153 
154 
155 	/**
156 	 * Overridden method that simply returns {@code null} when no
157 	 * ServletConfig set yet.
158 	 * @see #getServletConfig()
159 	 */
160 	@Override
161 	public final String getServletName() {
162 		return (getServletConfig() != null ? getServletConfig().getServletName() : null);
163 	}
164 
165 	/**
166 	 * Overridden method that simply returns {@code null} when no
167 	 * ServletConfig set yet.
168 	 * @see #getServletConfig()
169 	 */
170 	@Override
171 	public final ServletContext getServletContext() {
172 		return (getServletConfig() != null ? getServletConfig().getServletContext() : null);
173 	}
174 
175 
176 	/**
177 	 * Subclasses may override this to perform custom initialization.
178 	 * All bean properties of this servlet will have been set before this
179 	 * method is invoked.
180 	 * <p>This default implementation is empty.
181 	 * @throws ServletException if subclass initialization fails
182 	 */
183 	protected void initServletBean() throws ServletException {
184 	}
185 
186 	/**
187 	 * {@inheritDoc}
188 	 * @throws IllegalArgumentException if environment is not assignable to
189 	 * {@code ConfigurableEnvironment}.
190 	 */
191 	@Override
192 	public void setEnvironment(Environment environment) {
193 		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
194 		this.environment = (ConfigurableEnvironment) environment;
195 	}
196 
197 	/**
198 	 * {@inheritDoc}
199 	 * <p>If {@code null}, a new environment will be initialized via
200 	 * {@link #createEnvironment()}.
201 	 */
202 	@Override
203 	public ConfigurableEnvironment getEnvironment() {
204 		if (this.environment == null) {
205 			this.environment = this.createEnvironment();
206 		}
207 		return this.environment;
208 	}
209 
210 	/**
211 	 * Create and return a new {@link StandardServletEnvironment}. Subclasses may override
212 	 * in order to configure the environment or specialize the environment type returned.
213 	 */
214 	protected ConfigurableEnvironment createEnvironment() {
215 		return new StandardServletEnvironment();
216 	}
217 
218 
219 	/**
220 	 * PropertyValues implementation created from ServletConfig init parameters.
221 	 */
222 	private static class ServletConfigPropertyValues extends MutablePropertyValues {
223 
224 		/**
225 		 * Create new ServletConfigPropertyValues.
226 		 * @param config ServletConfig we'll use to take PropertyValues from
227 		 * @param requiredProperties set of property names we need, where
228 		 * we can't accept default values
229 		 * @throws ServletException if any required properties are missing
230 		 */
231 		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
232 			throws ServletException {
233 
234 			Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
235 					new HashSet<String>(requiredProperties) : null;
236 
237 			Enumeration<String> en = config.getInitParameterNames();
238 			while (en.hasMoreElements()) {
239 				String property = en.nextElement();
240 				Object value = config.getInitParameter(property);
241 				addPropertyValue(new PropertyValue(property, value));
242 				if (missingProps != null) {
243 					missingProps.remove(property);
244 				}
245 			}
246 
247 			// Fail if we are still missing properties.
248 			if (missingProps != null && missingProps.size() > 0) {
249 				throw new ServletException(
250 					"Initialization from ServletConfig for servlet '" + config.getServletName() +
251 					"' failed; the following required properties were missing: " +
252 					StringUtils.collectionToDelimitedString(missingProps, ", "));
253 			}
254 		}
255 	}
256 
257 }