La nuova specifica Portlet 2.0 (sancita nella JSR-286, http://jcp.org/en/jsr/detail?id=286) introduce il concetto di coordination between portlets, che si pone l'obiettivo di rendere le portlet cooperative tra di loro; ossia poter trasferire informazioni da una portlet ad un'altra.
Liferay 5.2.3 è compliant con le specifiche JSR-286, pertanto vediamo le modalità previste dalla specifica per la coordinazione:
- condivisione delle informazioni (all'interno della medesima web application) attraverso la sessione;
- utilizzo dei public render parameters accessibili da tutte le portlet;
- utilizzo della nuova piattaforma ad eventi.
La vera novità è la piattaforma ad eventi che consente un reale disaccoppiamento nel funzionamento delle portlet, in maniera molto elegante: una portlet genera un "evento" contenente le informazioni necessarie ed una o più portlet ricevono l'evento e si comportano di conseguenza.
Prima di procedere, vediamo cosa dice la specifica in termini di sequenza di invocazione; supponiamo quindi di avere una pagina di portale composta da 2 portlet che chiameremo A e B.
A seguito di un'azione su A (ad esempio un submit) il container invoca come prima cosa il metodo processAction()
di A; successivamente vengono invocati, in ordine non deterministico, tutti i metodi render()
di tutte le portlet (compresa A); infine la pagina viene composta e restituita al browser. Questo è il comportamento "standard".
Durante l'elaborazione del metodo processAction()
, A potrebbe decidere di generare uno o più eventi attraverso il metodo setEvent()
dell'interfaccia StateAwareResponse
(implementata sia da ActionResponse
che da EventResponse
). Terminato il metodo processAction()
, il container si accorge degli eventi generati ed invoca il metodo processEvent()
di tutte le portlet che sono registrate per gestire quello specifico evento (ad esempio la portlet B). Solamente al termine di tutti i metodi processEvent()
, il container invocherà i metodi render()
di tutte le portlet.
Vediamo quindi un esempio su come realizzare un'applicazione con 2 portlet Struts che cooperano tra di loro. Si suppone di avere a disposizione un ambiente ext di Liferay 5.2.3 configurato e pronto all'uso.
Innanzitutto va modificato il file portlet-ext.xml
per effettuare il censimento di tutti gli eventi che devono essere gestiti:
<portlet-app>
...
<default-namespace>http://www.d-vel.com/events</default-namespace>
<event-definition>
<name>viewDetail</name>
<value-type>java.lang.Integer</value-type>
</event-definition>
...
</portlet-app>
Nel caso specifico stiamo definendo un namespace di default che verrà utilizzato per tutti gli eventi ed un singolo evento di nome viewDetail
che conterrà un valore intero (sarà banalmente un ID). Il valore assunto da un evento può essere una qualsiasi classe, purchè compliant con le specifiche JAXB.
Dopodichè occorre specificare quale portlet genera l'evento e quale (o quali) invece lo consuma (vengono omessi gli elementi non essenziali):
<portlet-app>
...
<portlet>
<portlet-name>MasterPortlet</portlet-name>
<portlet-class>com.liferay.portlet.StrutsPortlet</portlet-class>
<init-param>
<name>view-action</name>
<value>/master/view</value>
</init-param>
...
<supported-publishing-event>
<name>viewDetail</name>
</supported-publishing-event>
...
</portlet>
<portlet>
<portlet-name>DetailPortlet</portlet-name>
<portlet-class>com.dvel.portlet.DetailPortlet</portlet-class>
<init-param>
<name>view-action</name>
<value>/detail/view</value>
</init-param>
...
<supported-processing-event>
<name>viewDetail</name>
</supported-processing-event>
...
</portlet>
...
</portlet-app>
Nel caso della portlet master è presente l'elemento <supported-publishing-event>
che contiene l'evento che può essere "pubblicato"; nel caso della portlet detail è presente l'elemento <supported-processing-event>
che contiene l'evento che può essere "processato". In entrambi i casi il nome dell'evento deve essere lo stesso censito in precedenza.
La portlet di dettaglio è di una classe personalizzata (che comunque estende StrutsPortlet
) in quanto deve consumare l'evento attraverso il metodo processEvent()
:
public class DetailPortlet extends StrutsPortlet {
@Override
public void processEvent(EventRequest request, EventResponse response)
throws PortletException, IOException {
Event event = request.getEvent();
if (event.getName().equals("viewDetail")) {
Integer value = (Integer) event.getValue();
request.getPortletSession().setAttribute("detailID", value);
}
}
}
Il metodo non fa altro che recuperare l'evento, verificare che sia quello atteso (potrebbero essere più di uno), estrarre il valore dall'evento ed inserirlo all'interno della sessione di portlet in modo da renderlo persistente; la specifica non prevede infatti alcuna propagazione dei parametri ai render successivi.
L'ultimo passaggio è quello di utilizzare il valore dell'evento nella action Struts e farne quello che serve:
public class ViewAction extends PortletAction {
@Override
public ActionForward render(ActionMapping mapping, ActionForm form,
PortletConfig portletConfig, RenderRequest renderRequest,
RenderResponse renderResponse) throws Exception {
Integer id = (Integer) renderRequest.getPortletSession().getAttribute("detailID");
...
}
}