001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.web.portlet.mvc;
018:
019: import javax.portlet.ActionRequest;
020: import javax.portlet.ActionResponse;
021: import javax.portlet.PortletException;
022: import javax.portlet.PortletSession;
023: import javax.portlet.RenderRequest;
024: import javax.portlet.RenderResponse;
025: import javax.portlet.WindowState;
026:
027: import org.springframework.web.portlet.ModelAndView;
028: import org.springframework.web.portlet.handler.PortletContentGenerator;
029: import 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>requiresSession</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</code> and
086: * <code>handleRenderRequestInternal</code> should be
087: * synchronized around the PortletSession, to serialize invocations
088: * from the same client. 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</code> 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</code>.
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: */
126: public abstract class AbstractController extends
127: PortletContentGenerator implements Controller {
128:
129: private boolean synchronizeOnSession = false;
130:
131: private boolean renderWhenMinimized = false;
132:
133: /**
134: * Set if controller execution should be synchronized on the session,
135: * to serialize parallel invocations from the same client.
136: * <p>More specifically, the execution of the <code>handleActionRequestInternal</code>
137: * method will get synchronized if this flag is "true". The best available
138: * session mutex will be used for the synchronization; ideally, this will
139: * be a mutex exposed by HttpSessionMutexListener.
140: * <p>The session mutex is guaranteed to be the same object during
141: * the entire lifetime of the session, available under the key defined
142: * by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
143: * safe reference to synchronize on for locking on the current session.
144: * <p>In many cases, the PortletSession reference itself is a safe mutex
145: * as well, since it will always be the same object reference for the
146: * same active logical session. However, this is not guaranteed across
147: * different servlet containers; the only 100% safe way is a session mutex.
148: * @see #handleActionRequestInternal
149: * @see org.springframework.web.util.HttpSessionMutexListener
150: * @see org.springframework.web.portlet.util.PortletUtils#getSessionMutex(javax.portlet.PortletSession)
151: */
152: public final void setSynchronizeOnSession(
153: boolean synchronizeOnSession) {
154: this .synchronizeOnSession = synchronizeOnSession;
155: }
156:
157: /**
158: * Return whether controller execution should be synchronized on the session.
159: */
160: public final boolean isSynchronizeOnSession() {
161: return synchronizeOnSession;
162: }
163:
164: /**
165: * Set if the controller should render an view when the portlet is in
166: * a minimized window. The default is false.
167: * @see javax.portlet.RenderRequest#getWindowState
168: * @see javax.portlet.WindowState#MINIMIZED
169: */
170: public void setRenderWhenMinimized(boolean renderWhenMinimized) {
171: this .renderWhenMinimized = renderWhenMinimized;
172: }
173:
174: /**
175: * Return whether controller will render when portlet is minimized.
176: */
177: public boolean isRenderWhenMinimized() {
178: return renderWhenMinimized;
179: }
180:
181: public final void handleActionRequest(ActionRequest request,
182: ActionResponse response) throws Exception {
183:
184: // Delegate to PortletContentGenerator for checking and preparing.
185: check(request, response);
186:
187: // Execute in synchronized block if required.
188: if (this .synchronizeOnSession) {
189: PortletSession session = request.getPortletSession(false);
190: if (session != null) {
191: synchronized (session) {
192: handleActionRequestInternal(request, response);
193: return;
194: }
195: }
196: }
197:
198: handleActionRequestInternal(request, response);
199: }
200:
201: public final ModelAndView handleRenderRequest(
202: RenderRequest request, RenderResponse response)
203: throws Exception {
204:
205: // If the portlet is minimized and we don't want to render then return null.
206: if (WindowState.MINIMIZED.equals(request.getWindowState())
207: && !this .renderWhenMinimized) {
208: return null;
209: }
210:
211: // Delegate to PortletContentGenerator for checking and preparing.
212: checkAndPrepare(request, response);
213:
214: // Execute in synchronized block if required.
215: if (this .synchronizeOnSession) {
216: PortletSession session = request.getPortletSession(false);
217: if (session != null) {
218: Object mutex = PortletUtils.getSessionMutex(session);
219: synchronized (mutex) {
220: return handleRenderRequestInternal(request,
221: response);
222: }
223: }
224: }
225:
226: return handleRenderRequestInternal(request, response);
227: }
228:
229: /**
230: * Subclasses are meant to override this method if the controller
231: * is expected to handle action requests. The contract is the same as
232: * for <code>handleActionRequest</code>.
233: * <p>The default implementation throws a PortletException.
234: * @see #handleActionRequest
235: * @see #handleRenderRequestInternal
236: */
237: protected void handleActionRequestInternal(ActionRequest request,
238: ActionResponse response) throws Exception {
239:
240: throw new PortletException("[" + getClass().getName()
241: + "] does not handle action requests");
242: }
243:
244: /**
245: * Subclasses are meant to override this method if the controller
246: * is expected to handle render requests. The contract is the same as
247: * for <code>handleRenderRequest</code>.
248: * <p>The default implementation throws a PortletException.
249: * @see #handleRenderRequest
250: * @see #handleActionRequestInternal
251: */
252: protected ModelAndView handleRenderRequestInternal(
253: RenderRequest request, RenderResponse response)
254: throws Exception {
255:
256: throw new PortletException("[" + getClass().getName()
257: + "] does not handle render requests");
258: }
259:
260: }
|