001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet;
018
019import java.util.Enumeration;
020import java.util.HashSet;
021import java.util.Set;
022import javax.servlet.ServletConfig;
023import javax.servlet.ServletContext;
024import javax.servlet.ServletException;
025import javax.servlet.http.HttpServlet;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.beans.BeanWrapper;
031import org.springframework.beans.BeansException;
032import org.springframework.beans.MutablePropertyValues;
033import org.springframework.beans.PropertyAccessorFactory;
034import org.springframework.beans.PropertyValue;
035import org.springframework.beans.PropertyValues;
036import org.springframework.context.EnvironmentAware;
037import org.springframework.core.env.ConfigurableEnvironment;
038import org.springframework.core.env.Environment;
039import org.springframework.core.env.EnvironmentCapable;
040import org.springframework.core.io.Resource;
041import org.springframework.core.io.ResourceEditor;
042import org.springframework.core.io.ResourceLoader;
043import org.springframework.util.Assert;
044import org.springframework.util.CollectionUtils;
045import org.springframework.util.StringUtils;
046import org.springframework.web.context.support.ServletContextResourceLoader;
047import org.springframework.web.context.support.StandardServletEnvironment;
048
049/**
050 * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
051 * its config parameters ({@code init-param} entries within the
052 * {@code servlet} tag in {@code web.xml}) as bean properties.
053 *
054 * <p>A handy superclass for any type of servlet. Type conversion of config
055 * parameters is automatic, with the corresponding setter method getting
056 * invoked with the converted value. It is also possible for subclasses to
057 * specify required properties. Parameters without matching bean property
058 * setter will simply be ignored.
059 *
060 * <p>This servlet leaves request handling to subclasses, inheriting the default
061 * behavior of HttpServlet ({@code doGet}, {@code doPost}, etc).
062 *
063 * <p>This generic servlet base class has no dependency on the Spring
064 * {@link org.springframework.context.ApplicationContext} concept. Simple
065 * servlets usually don't load their own context but rather access service
066 * beans from the Spring root application context, accessible via the
067 * filter's {@link #getServletContext() ServletContext} (see
068 * {@link org.springframework.web.context.support.WebApplicationContextUtils}).
069 *
070 * <p>The {@link FrameworkServlet} class is a more specific servlet base
071 * class which loads its own application context. FrameworkServlet serves
072 * as direct base class of Spring's full-fledged {@link DispatcherServlet}.
073 *
074 * @author Rod Johnson
075 * @author Juergen Hoeller
076 * @see #addRequiredProperty
077 * @see #initServletBean
078 * @see #doGet
079 * @see #doPost
080 */
081@SuppressWarnings("serial")
082public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
083
084        /** Logger available to subclasses */
085        protected final Log logger = LogFactory.getLog(getClass());
086
087        private ConfigurableEnvironment environment;
088
089        private final Set<String> requiredProperties = new HashSet<String>(4);
090
091
092        /**
093         * Subclasses can invoke this method to specify that this property
094         * (which must match a JavaBean property they expose) is mandatory,
095         * and must be supplied as a config parameter. This should be called
096         * from the constructor of a subclass.
097         * <p>This method is only relevant in case of traditional initialization
098         * driven by a ServletConfig instance.
099         * @param property name of the required property
100         */
101        protected final void addRequiredProperty(String property) {
102                this.requiredProperties.add(property);
103        }
104
105        /**
106         * Set the {@code Environment} that this servlet runs in.
107         * <p>Any environment set here overrides the {@link StandardServletEnvironment}
108         * provided by default.
109         * @throws IllegalArgumentException if environment is not assignable to
110         * {@code ConfigurableEnvironment}
111         */
112        @Override
113        public void setEnvironment(Environment environment) {
114                Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "ConfigurableEnvironment required");
115                this.environment = (ConfigurableEnvironment) environment;
116        }
117
118        /**
119         * Return the {@link Environment} associated with this servlet.
120         * <p>If none specified, a default environment will be initialized via
121         * {@link #createEnvironment()}.
122         */
123        @Override
124        public ConfigurableEnvironment getEnvironment() {
125                if (this.environment == null) {
126                        this.environment = createEnvironment();
127                }
128                return this.environment;
129        }
130
131        /**
132         * Create and return a new {@link StandardServletEnvironment}.
133         * <p>Subclasses may override this in order to configure the environment or
134         * specialize the environment type returned.
135         */
136        protected ConfigurableEnvironment createEnvironment() {
137                return new StandardServletEnvironment();
138        }
139
140        /**
141         * Map config parameters onto bean properties of this servlet, and
142         * invoke subclass initialization.
143         * @throws ServletException if bean properties are invalid (or required
144         * properties are missing), or if subclass initialization fails.
145         */
146        @Override
147        public final void init() throws ServletException {
148                if (logger.isDebugEnabled()) {
149                        logger.debug("Initializing servlet '" + getServletName() + "'");
150                }
151
152                // Set bean properties from init parameters.
153                PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
154                if (!pvs.isEmpty()) {
155                        try {
156                                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
157                                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
158                                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
159                                initBeanWrapper(bw);
160                                bw.setPropertyValues(pvs, true);
161                        }
162                        catch (BeansException ex) {
163                                if (logger.isErrorEnabled()) {
164                                        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
165                                }
166                                throw ex;
167                        }
168                }
169
170                // Let subclasses do whatever initialization they like.
171                initServletBean();
172
173                if (logger.isDebugEnabled()) {
174                        logger.debug("Servlet '" + getServletName() + "' configured successfully");
175                }
176        }
177
178        /**
179         * Initialize the BeanWrapper for this HttpServletBean,
180         * possibly with custom editors.
181         * <p>This default implementation is empty.
182         * @param bw the BeanWrapper to initialize
183         * @throws BeansException if thrown by BeanWrapper methods
184         * @see org.springframework.beans.BeanWrapper#registerCustomEditor
185         */
186        protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
187        }
188
189        /**
190         * Subclasses may override this to perform custom initialization.
191         * All bean properties of this servlet will have been set before this
192         * method is invoked.
193         * <p>This default implementation is empty.
194         * @throws ServletException if subclass initialization fails
195         */
196        protected void initServletBean() throws ServletException {
197        }
198
199        /**
200         * Overridden method that simply returns {@code null} when no
201         * ServletConfig set yet.
202         * @see #getServletConfig()
203         */
204        @Override
205        public final String getServletName() {
206                return (getServletConfig() != null ? getServletConfig().getServletName() : null);
207        }
208
209        /**
210         * Overridden method that simply returns {@code null} when no
211         * ServletConfig set yet.
212         * @see #getServletConfig()
213         */
214        @Override
215        public final ServletContext getServletContext() {
216                return (getServletConfig() != null ? getServletConfig().getServletContext() : null);
217        }
218
219
220        /**
221         * PropertyValues implementation created from ServletConfig init parameters.
222         */
223        private static class ServletConfigPropertyValues extends MutablePropertyValues {
224
225                /**
226                 * Create new ServletConfigPropertyValues.
227                 * @param config ServletConfig we'll use to take PropertyValues from
228                 * @param requiredProperties set of property names we need, where
229                 * we can't accept default values
230                 * @throws ServletException if any required properties are missing
231                 */
232                public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
233                                throws ServletException {
234
235                        Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
236                                        new HashSet<String>(requiredProperties) : null);
237
238                        Enumeration<String> paramNames = config.getInitParameterNames();
239                        while (paramNames.hasMoreElements()) {
240                                String property = paramNames.nextElement();
241                                Object value = config.getInitParameter(property);
242                                addPropertyValue(new PropertyValue(property, value));
243                                if (missingProps != null) {
244                                        missingProps.remove(property);
245                                }
246                        }
247
248                        // Fail if we are still missing properties.
249                        if (!CollectionUtils.isEmpty(missingProps)) {
250                                throw new ServletException(
251                                                "Initialization from ServletConfig for servlet '" + config.getServletName() +
252                                                "' failed; the following required properties were missing: " +
253                                                StringUtils.collectionToDelimitedString(missingProps, ", "));
254                        }
255                }
256        }
257
258}