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}