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}