001/* 002 * Copyright 2002-2014 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.portlet.mvc; 018 019import javax.portlet.ActionRequest; 020import javax.portlet.ActionResponse; 021import javax.portlet.PortletException; 022import javax.portlet.PortletSession; 023import javax.portlet.RenderRequest; 024import javax.portlet.RenderResponse; 025import javax.portlet.WindowState; 026 027import org.springframework.web.portlet.ModelAndView; 028import org.springframework.web.portlet.handler.PortletContentGenerator; 029import org.springframework.web.portlet.util.PortletUtils; 030 031/** 032 * Convenient superclass for controller implementations, using the Template 033 * Method design pattern. 034 * 035 * <p>As stated in the {@link Controller Controller} 036 * interface, a lot of functionality is already provided by certain abstract 037 * base controllers. The AbstractController is one of the most important 038 * abstract base controller providing basic features such controlling if a 039 * session is required and render caching. 040 * 041 * <p><b><a name="workflow">Workflow 042 * (<a href="Controller.html#workflow">and that defined by interface</a>):</b><br> 043 * <ol> 044 * <li>If this is an action request, {@link #handleActionRequest handleActionRequest} 045 * will be called by the DispatcherPortlet once to perform the action defined by this 046 * controller.</li> 047 * <li>If a session is required, try to get it (PortletException if not found).</li> 048 * <li>Call method {@link #handleActionRequestInternal handleActionRequestInternal}, 049 * (optionally synchronizing around the call on the PortletSession), 050 * which should be overridden by extending classes to provide actual functionality to 051 * perform the desired action of the controller. This will be executed only once.</li> 052 * <li>For a straight render request, or the render phase of an action request (assuming the 053 * same controller is called for the render phase -- see tip below), 054 * {@link #handleRenderRequest handleRenderRequest} will be called by the DispatcherPortlet 055 * repeatedly to render the display defined by this controller.</li> 056 * <li>If a session is required, try to get it (PortletException if none found).</li> 057 * <li>It will control caching as defined by the cacheSeconds property.</li> 058 * <li>Call method {@link #handleRenderRequestInternal handleRenderRequestInternal}, 059 * (optionally synchronizing around the call on the PortletSession), 060 * which should be overridden by extending classes to provide actual functionality to 061 * return {@link org.springframework.web.portlet.ModelAndView ModelAndView} objects. 062 * This will be executed repeatedly as the portal updates the current displayed page.</li> 063 * </ol> 064 * 065 * <p><b><a name="config">Exposed configuration properties</a> 066 * (<a href="Controller.html#config">and those defined by interface</a>):</b><br> 067 * <table border="1"> 068 * <tr> 069 * <td><b>name</b></th> 070 * <td><b>default</b></td> 071 * <td><b>description</b></td> 072 * </tr> 073 * <tr> 074 * <td>requireSession</td> 075 * <td>false</td> 076 * <td>whether a session should be required for requests to be able to 077 * be handled by this controller. This ensures, derived controller 078 * can - without fear of Nullpointers - call request.getSession() to 079 * retrieve a session. If no session can be found while processing 080 * the request, a PortletException will be thrown</td> 081 * </tr> 082 * <tr> 083 * <td>synchronizeOnSession</td> 084 * <td>false</td> 085 * <td>whether the calls to {@code handleRenderRequestInternal} and 086 * {@code handleRenderRequestInternal} should be synchronized around 087 * the PortletSession, to serialize invocations from the same client. 088 * No effect if there is no PortletSession. 089 * </td> 090 * </tr> 091 * <tr> 092 * <td>cacheSeconds</td> 093 * <td>-1</td> 094 * <td>indicates the amount of seconds to specify caching is allowed in 095 * the render response generatedby this request. 0 (zero) will indicate 096 * no caching is allowed at all, -1 (the default) will not override the 097 * portlet configuration and any positive number will cause the render 098 * response to declare the amount indicated as seconds to cache the content</td> 099 * </tr> 100 * <tr> 101 * <td>renderWhenMinimized</td> 102 * <td>false</td> 103 * <td>whether should be rendered when the portlet is in a minimized state -- 104 * will return null for the ModelandView when the portlet is minimized 105 * and this is false</td> 106 * </tr> 107 * </table> 108 * 109 * <p><b>TIP:</b> The controller mapping will be run twice by the PortletDispatcher for 110 * action requests -- once for the action phase and again for the render phase. You can 111 * reach the render phase of a different controller by simply changing the values for the 112 * criteria your mapping is using, such as portlet mode or a request parameter, during the 113 * action phase of your controller. This is very handy since redirects within the portlet 114 * are apparently impossible. Before doing this, it is usually wise to call 115 * {@code clearAllRenderParameters} and then explicitly set all the parameters that 116 * you want the new controller to see. This avoids unexpected parameters from being passed 117 * to the render phase of the second controller, such as the parameter indicating a form 118 * submit ocurred in an {@code AbstractFormController}. 119 * 120 * <p>Thanks to Rainer Schmitz and Nick Lothian for their suggestions! 121 * 122 * @author John A. Lewis 123 * @author Juergen Hoeller 124 * @since 2.0 125 * @see ResourceAwareController 126 * @see EventAwareController 127 */ 128public abstract class AbstractController extends PortletContentGenerator implements Controller { 129 130 private boolean synchronizeOnSession = false; 131 132 private boolean renderWhenMinimized = false; 133 134 135 /** 136 * Set if controller execution should be synchronized on the session, 137 * to serialize parallel invocations from the same client. 138 * <p>More specifically, the execution of the {@code handleActionRequestInternal} 139 * method will get synchronized if this flag is "true". The best available 140 * session mutex will be used for the synchronization; ideally, this will 141 * be a mutex exposed by HttpSessionMutexListener. 142 * <p>The session mutex is guaranteed to be the same object during 143 * the entire lifetime of the session, available under the key defined 144 * by the {@code SESSION_MUTEX_ATTRIBUTE} constant. It serves as a 145 * safe reference to synchronize on for locking on the current session. 146 * <p>In many cases, the PortletSession reference itself is a safe mutex 147 * as well, since it will always be the same object reference for the 148 * same active logical session. However, this is not guaranteed across 149 * different servlet containers; the only 100% safe way is a session mutex. 150 * @see #handleActionRequestInternal 151 * @see org.springframework.web.util.HttpSessionMutexListener 152 * @see org.springframework.web.portlet.util.PortletUtils#getSessionMutex(javax.portlet.PortletSession) 153 */ 154 public final void setSynchronizeOnSession(boolean synchronizeOnSession) { 155 this.synchronizeOnSession = synchronizeOnSession; 156 } 157 158 /** 159 * Return whether controller execution should be synchronized on the session. 160 */ 161 public final boolean isSynchronizeOnSession() { 162 return this.synchronizeOnSession; 163 } 164 165 /** 166 * Set if the controller should render an view when the portlet is in 167 * a minimized window. The default is false. 168 * @see javax.portlet.RenderRequest#getWindowState 169 * @see javax.portlet.WindowState#MINIMIZED 170 */ 171 public final void setRenderWhenMinimized(boolean renderWhenMinimized) { 172 this.renderWhenMinimized = renderWhenMinimized; 173 } 174 175 /** 176 * Return whether controller will render when portlet is minimized. 177 */ 178 public final boolean isRenderWhenMinimized() { 179 return this.renderWhenMinimized; 180 } 181 182 183 @Override 184 public void handleActionRequest(ActionRequest request, ActionResponse response) throws Exception { 185 // Delegate to PortletContentGenerator for checking and preparing. 186 check(request, response); 187 188 // Execute in synchronized block if required. 189 if (this.synchronizeOnSession) { 190 PortletSession session = request.getPortletSession(false); 191 if (session != null) { 192 Object mutex = PortletUtils.getSessionMutex(session); 193 synchronized (mutex) { 194 handleActionRequestInternal(request, response); 195 return; 196 } 197 } 198 } 199 200 handleActionRequestInternal(request, response); 201 } 202 203 @Override 204 public ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response) throws Exception { 205 // If the portlet is minimized and we don't want to render then return null. 206 if (WindowState.MINIMIZED.equals(request.getWindowState()) && !this.renderWhenMinimized) { 207 return null; 208 } 209 210 // Delegate to PortletContentGenerator for checking and preparing. 211 checkAndPrepare(request, response); 212 213 // Execute in synchronized block if required. 214 if (this.synchronizeOnSession) { 215 PortletSession session = request.getPortletSession(false); 216 if (session != null) { 217 Object mutex = PortletUtils.getSessionMutex(session); 218 synchronized (mutex) { 219 return handleRenderRequestInternal(request, response); 220 } 221 } 222 } 223 224 return handleRenderRequestInternal(request, response); 225 } 226 227 228 /** 229 * Subclasses are meant to override this method if the controller 230 * is expected to handle action requests. The contract is the same as 231 * for {@code handleActionRequest}. 232 * <p>The default implementation throws a PortletException. 233 * @see #handleActionRequest 234 * @see #handleRenderRequestInternal 235 */ 236 protected void handleActionRequestInternal(ActionRequest request, ActionResponse response) 237 throws Exception { 238 239 throw new PortletException("[" + getClass().getName() + "] does not handle action requests"); 240 } 241 242 /** 243 * Subclasses are meant to override this method if the controller 244 * is expected to handle render requests. The contract is the same as 245 * for {@code handleRenderRequest}. 246 * <p>The default implementation throws a PortletException. 247 * @see #handleRenderRequest 248 * @see #handleActionRequestInternal 249 */ 250 protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response) 251 throws Exception { 252 253 throw new PortletException("[" + getClass().getName() + "] does not handle render requests"); 254 } 255 256}