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.mvc; 018 019import javax.servlet.http.HttpServletRequest; 020import javax.servlet.http.HttpServletResponse; 021import javax.servlet.http.HttpSession; 022 023import org.springframework.http.HttpMethod; 024import org.springframework.lang.Nullable; 025import org.springframework.web.servlet.ModelAndView; 026import org.springframework.web.servlet.support.WebContentGenerator; 027import org.springframework.web.util.WebUtils; 028 029/** 030 * Convenient superclass for controller implementations, using the Template Method 031 * design pattern. 032 * 033 * <p><b>Workflow 034 * (<a href="Controller.html#workflow">and that defined by interface</a>):</b><br> 035 * <ol> 036 * <li>{@link #handleRequest(HttpServletRequest, HttpServletResponse) handleRequest()} 037 * will be called by the DispatcherServlet</li> 038 * <li>Inspection of supported methods (ServletException if request method 039 * is not support)</li> 040 * <li>If session is required, try to get it (ServletException if not found)</li> 041 * <li>Set caching headers if needed according to the cacheSeconds property</li> 042 * <li>Call abstract method 043 * {@link #handleRequestInternal(HttpServletRequest, HttpServletResponse) handleRequestInternal()} 044 * (optionally synchronizing around the call on the HttpSession), 045 * which should be implemented by extending classes to provide actual 046 * functionality to return {@link org.springframework.web.servlet.ModelAndView ModelAndView} objects.</li> 047 * </ol> 048 * 049 * <p><b><a name="config">Exposed configuration properties</a> 050 * (<a href="Controller.html#config">and those defined by interface</a>):</b><br> 051 * <table border="1"> 052 * <tr> 053 * <td><b>name</b></td> 054 * <td><b>default</b></td> 055 * <td><b>description</b></td> 056 * </tr> 057 * <tr> 058 * <td>supportedMethods</td> 059 * <td>GET,POST</td> 060 * <td>comma-separated (CSV) list of methods supported by this controller, 061 * such as GET, POST and PUT</td> 062 * </tr> 063 * <tr> 064 * <td>requireSession</td> 065 * <td>false</td> 066 * <td>whether a session should be required for requests to be able to 067 * be handled by this controller. This ensures that derived controller 068 * can - without fear of null pointers - call request.getSession() to 069 * retrieve a session. If no session can be found while processing 070 * the request, a ServletException will be thrown</td> 071 * </tr> 072 * <tr> 073 * <td>cacheSeconds</td> 074 * <td>-1</td> 075 * <td>indicates the amount of seconds to include in the cache header 076 * for the response following on this request. 0 (zero) will include 077 * headers for no caching at all, -1 (the default) will not generate 078 * <i>any headers</i> and any positive number will generate headers 079 * that state the amount indicated as seconds to cache the content</td> 080 * </tr> 081 * <tr> 082 * <td>synchronizeOnSession</td> 083 * <td>false</td> 084 * <td>whether the call to {@code handleRequestInternal} should be 085 * synchronized around the HttpSession, to serialize invocations 086 * from the same client. No effect if there is no HttpSession. 087 * </td> 088 * </tr> 089 * </table> 090 * 091 * @author Rod Johnson 092 * @author Juergen Hoeller 093 * @author Rossen Stoyanchev 094 * @see WebContentInterceptor 095 */ 096public abstract class AbstractController extends WebContentGenerator implements Controller { 097 098 private boolean synchronizeOnSession = false; 099 100 101 /** 102 * Create a new AbstractController which supports 103 * HTTP methods GET, HEAD and POST by default. 104 */ 105 public AbstractController() { 106 this(true); 107 } 108 109 /** 110 * Create a new AbstractController. 111 * @param restrictDefaultSupportedMethods {@code true} if this 112 * controller should support HTTP methods GET, HEAD and POST by default, 113 * or {@code false} if it should be unrestricted 114 * @since 4.3 115 */ 116 public AbstractController(boolean restrictDefaultSupportedMethods) { 117 super(restrictDefaultSupportedMethods); 118 } 119 120 121 /** 122 * Set if controller execution should be synchronized on the session, 123 * to serialize parallel invocations from the same client. 124 * <p>More specifically, the execution of the {@code handleRequestInternal} 125 * method will get synchronized if this flag is "true". The best available 126 * session mutex will be used for the synchronization; ideally, this will 127 * be a mutex exposed by HttpSessionMutexListener. 128 * <p>The session mutex is guaranteed to be the same object during 129 * the entire lifetime of the session, available under the key defined 130 * by the {@code SESSION_MUTEX_ATTRIBUTE} constant. It serves as a 131 * safe reference to synchronize on for locking on the current session. 132 * <p>In many cases, the HttpSession reference itself is a safe mutex 133 * as well, since it will always be the same object reference for the 134 * same active logical session. However, this is not guaranteed across 135 * different servlet containers; the only 100% safe way is a session mutex. 136 * @see AbstractController#handleRequestInternal 137 * @see org.springframework.web.util.HttpSessionMutexListener 138 * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession) 139 */ 140 public final void setSynchronizeOnSession(boolean synchronizeOnSession) { 141 this.synchronizeOnSession = synchronizeOnSession; 142 } 143 144 /** 145 * Return whether controller execution should be synchronized on the session. 146 */ 147 public final boolean isSynchronizeOnSession() { 148 return this.synchronizeOnSession; 149 } 150 151 152 @Override 153 @Nullable 154 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) 155 throws Exception { 156 157 if (HttpMethod.OPTIONS.matches(request.getMethod())) { 158 response.setHeader("Allow", getAllowHeader()); 159 return null; 160 } 161 162 // Delegate to WebContentGenerator for checking and preparing. 163 checkRequest(request); 164 prepareResponse(response); 165 166 // Execute handleRequestInternal in synchronized block if required. 167 if (this.synchronizeOnSession) { 168 HttpSession session = request.getSession(false); 169 if (session != null) { 170 Object mutex = WebUtils.getSessionMutex(session); 171 synchronized (mutex) { 172 return handleRequestInternal(request, response); 173 } 174 } 175 } 176 177 return handleRequestInternal(request, response); 178 } 179 180 /** 181 * Template method. Subclasses must implement this. 182 * The contract is the same as for {@code handleRequest}. 183 * @see #handleRequest 184 */ 185 @Nullable 186 protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) 187 throws Exception; 188 189}