Dokumentáció
A Liferay az erőforrások és az ismeretek gazdag tárházát kínálja, hogy elősegítse technológiájának a közösség általi jobb használatát és alkalmazását.
Understanding the Two phases of Portlet Execution
One of the characteristics of portlet development that confuses most developers used to regular servlet development or who are used to other environments such as PHP, Python or Ruby is the need for two phases. The good news is that once you get used to them they become simple and useful.
The reason why two phases are needed is because a portlet does not own a whole HTML page, it only generates a fragment of it. The portal that holds the portlet is the one responsible for generating the page by invoking one or several portlets and adding some additional HTML around them. Usually, when the user interacts with the page, for example by clicking a link or a button, she's doing it within a specific portlet. The portal must forward the action performed by the user to that portlet and after that it must render the whole page, showing the content of that portlet, which may have changed, and also the content of the other portlets. For the other portlets in the page which have not been invoked by the user, what the portal does to get their content is to repeat the last invocation again (since it assumes it will yield the same result).
Now imagine this scenario: we have a page with two portlets, a navigation portlet and a shopping portlet. A user comes to the page and does the following:
Load the page
Clicks a button on the shopping portlet that automatically charges an amount on her credit card and starts a process to ship her the product she just bought. After this operation the portal also invokes the navigation portlet with its default view.
Click a link in the navigation portlet which causes the content of the portlet to change. After that the portal must also show the content of the shopping portlet, so it repeats the last action (the one in which the user clicked a button), which causes a new charge on the credit card and the start of a new shipping process.
I guess that by now you can tell that this is not right. Since the portal doesn't know whether the last operation on a portlet was an action or not, it would have no option but to repeat it over and over to obtain the content of the portlet again (at least until the Credit Card reached its limit).
Fortunately portals don't work that way. In order to prevent situations like the one described above, the portlet specification defines two phases for every request of a portlet, to allow the portal to differentiate when an action is being performed (and should not be repeated) and when the content is being produced (rendered):
Action phase: The action phase can only be invoked for one portlet at a time and is usually the result of an user interaction with the portlet. In this phase the portlet can change its status, for instance changing the user preferences of the portlet. It is also recommended that any inserts and modifications in the database or operations that should not be repeated are performed in this phase.
Render phase: The render phase is always invoked for all portlets in the page after the action phase (which may or not exist). This includes the portlet that also had executed its action phase. It's important to note that the order in which the render phase of the portlets in a page gets executedis not guaranteed by the portlet specification. Liferay has an extension to the specification through the element render-weight in liferay-portlet.xml. Portlets with a higher render weight will be rendered before those with a lower value.
In our example, so far we have used a portlet class called MVCPortlet. That is all that the portlet if it only has a render phase. In order to be able to add custom code that will be executed in the action phase (and thus will not be executed when the portlet is shown again) you need to create a subclass of MVCPortlet or directly a subclass of GenericPortlet if you don't want to use the lightweight Liferay's framework.
Our example above could be enhanced by creating the following class:
package com.liferay.samples;
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletPreferences;
import com.liferay.util.bridges.mvc.MVCPortlet;
public class MyGreetingPortlet extends MVCPortlet {
@Override
public void processAction(
ActionRequest actionRequest, ActionResponse actionResponse)
throws IOException, PortletException {
PortletPreferences prefs = actionRequest.getPreferences();
String greeting = actionRequest.getParameter("greeting");
if (greeting != null) {
prefs.setValue("greeting", greeting);
prefs.store();
}
super.processAction(actionRequest, actionResponse);
}
}
The file portlet.xml also needs to be changed so that it points to our new class:
<portlet>
<portlet-name>my-greeting</portlet-name>
<display-name>My Greeting</display-name>
<portlet-class>com.liferay.samples.MyGreetingPortlet</portlet-class>
<init-param>
<name>view-jsp</name>
<value>/view.jsp</value>
</init-param>
…
Finally, you will need to do a minor change in the edit.jsp file and change the URL to which the form is sent to let the portal know that it should execute the action phase. This is the perfect moment for you to know that there are three types of URLs that can be generated by a portlet:
renderURL: this is the type of URL that we have used so far. It invokes a portlet using only its render phase.
actionURL: this type of URL tells the portlet that it should execute its action phase before rendering all the portlets in the page.
resourceURL: this type of URL can be used to retrieve images, XML, JSON or any other type of resource. It is often used to generate images or other media types dynamically. It is very useful also to make AJAX requests to the server. The key difference of this URL type in comparison to the other two is that the portlet has full control of the data that will be sent in response.
So we must change the edit.jsp to use an actionURL by using the JSP tag of the same name. We also remove the previous code that was saving the preference:
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%@ page import="com.liferay.portal.kernel.util.ParamUtil" %>
<%@ page import="com.liferay.portal.kernel.util.Validator" %>
<%@ page import="javax.portlet.PortletPreferences" %>
<portlet:defineObjects />
<%
PortletPreferences prefs = renderRequest.getPreferences();
String greeting = (String)prefs.getValue(
"greeting", "Hello! Welcome to our portal.");
%>
<portlet:actionURL var="editGreetingURL">
<portlet:param name="jspPage" value="/edit.jsp" />
</portlet:actionURL>
<aui:form action="<%= editGreetingURL %>" method="post">
<aui:input label="greeting" name="greeting" type="text" value="<%= greeting %>" />
<aui:button type="submit" />
</aui:form>
<portlet:renderURL var="viewGreetingURL">
<portlet:param name="jspPage" value="/view.jsp" />
</portlet:renderURL>
<p><a href="<%= viewGreetingURL %>">← Back</a></p>
Try deploying again the portlet after making these changes, everything should work exactly like before.
Well, almost. If you have paid close attention you may have missed something, now the portlet is no longer showing a message to the user to let him know that the preference has been saved right after clicking the save button. In order to implement that we must have a way to pass information from the action phase to the render phase, so that the JSP can know that the preference has just been saved and then show a message to the user.