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