Partendo dai concetti base affrontiamo come si implementa una portlet e quali sono gli stru-menti messi a disposizione dalla API per gestire il ciclo di vita della request e della response
Un po‘ di formalismi
Prima di entrare nella programmazione delle portlet è utile introdurre alcune definizioni e concetti basilari. Per prima cosa si tenga a mente che una portlet serve per gestire una porzione del portale (in gergo una portlet-window) che è normalmente rappresentata da un box rettangolare. I vari box (il cui aspetto può variare molto da caso a caso a seconda del layout e dei CSS utilizzati) sono organizzati in modo da comporre la pagina del portale nel suo complesso (detta appunto portal-page).
Figura 1: terminologia relativa alle componenti di un portale portlet-based
Ogni portlet, in un determinato istante, può assumere diversi stati e modalità di visualizzazione. Per quanto concerne la modalità una portlet può trovarsi in uno dei seguenti modi:
- View mode: è la modalità standard con la quale la porlet visualizza un contenuto o consente di interagire con l‘utente durante il suo normale svolgimento del lavoro. Ad esempio una portlet che mostra le previsioni del tempo o un canale RSS, in view mode mostrerà il tempo su una determinata città o le notizie di un certo canale.
Figura 2: la Wheather portlet in modalità di VIEW: l‘utente visualizza
le previsioni meteorologiche di una determinata città
- Edit mode: è la modalità con la quale in genere l‘utente esegue modifiche sui parametri operativi della portlet. Ad esempio in edit mode la portlet delle previsioni del tempo permetterebbe di scegliere la città della quale visualizzare le previsioni meteo.
Figura 3: la Wheather portlet in modalità di edit: l‘utente sceglie la città
per la quale visualizzare le previsione meteorologiche
- Help mode: è una modalità non sempre implementata in una portlet e si limita a visualizzare un messaggio di aiuto sul funzionamento della portlet, i credits, eventuali link a pagine URL su internet dove possono essere presenti ulteriori risorse di approfondimento della portlet.
In genere all‘interno del file di configurazione della portlet (portlet.xml) è necessario specificare quali modalità la portlet è in grado supportare nel seguente modo
La specifica consente di definire ulteriori modalità custom delle portlet: in questo caso è compito del produttore del portal container di implementare le funzionalità necessarie a supportare tali modalità .
Il programmatore della portlet potrà quindi inserire nella propria portlet il codice necessario (Java e XML) per implementare nel componente tali modalità .
Una portlet può essere visualizzata all‘interno della portlet window in tre stati differenti:
- minimized state: la portlet non viene visualizzata, se non al limite la title bar (dipende dal layout scelto per comporre l‘aspetto finale)
- maximized state: la portlet assume tutto lo spazio a disposizione nella pagina, nascondendo le altre portlet.
- normal state: la portlet viene visualizzata in modalità normale occupando tutto lo spazio a disposizione all?interno della propria portlet-window
Figura 4: la portlet visualizzata in modalità minimized
Il modello request response
Si immagini di dover visualizzare una pagina HTML che sia il risultato di diverse operazioni di composizione: anche se è in parte inesatto o incompleto, possiamo assimilare tale pagina al portale nel suo complesso.
Per comprendere a pieno le potenzialità di un sistema basato su portlet, conviene partire da una situazione basata sulla tecnologia servlet, dove cioè le differenti aree del portale siano il risultato delle operazioni di vari oggetti, ad esempio di n servlet.
Ogni volta che un client esegue una richiesta su tale pagina, tutte le singole servlet saranno coinvolte nella produzione del contenuto finale.
Appare piuttosto evidente che in uno scenario del genere l?utilizzo di servlet (ma si avrebbe lo stesso risultato se usassimo pagine JSP) è quindi fortemente penalizzante, dato che, a fronte di una request HTTP, sarebbero coinvolte n servlet che dovrebbero essere eseguite tutte indistintamente.
Il modello architetturale delle portlet si propone di risolvere proprio questo problema: nello scenario appena proposto, a fronte di una richiesta del client, verrebbero eseguite solo i componenti (portlet) responsabili dell?aggiornamento delle parti da modificare.
Richieste di azione e richieste di disegno
Per come funziona il modello request-response è chiaro che ogni richiesta HTTP è associata a un solo punto della pagina (per essere ancora più chiari si può dire che l‘utente può inviare una request ciccando su un solo link per volta): per questo, sempre prendendo in esame la pagina del portale composta di varie parti, solo un punto sarà oggetto delle request, e quindi solo tale zona dovrà in qualche modo interagire con l‘utente. Le altre parti della pagina presumibilmente non saranno coinvolte direttamente o al massimo dovranno modificare il loro stato in modo passivo in funzione del cambiamento globale dello stato del portale.
Ad esempio si pensi al caso in cui l‘utente decida di cambiare lingua per la visualizzazione del portale: l‘utente in tal caso clicca su un link per scegliere la lingua, cosa che sul server provoca l‘esecuzione di una qualche logica che effettua le modifiche sullo stato complessivo del sistema per la scelta della lingua e per il caricamento del Locale corrispondente (secondo il modello della i18n supportato in Java).
A questo punto il sistema ha cambiato il suo stato e quindi tutte le altre parti del portale (ovvero tutte le altre portlet-window) dovranno modificare l‘output in modo da adeguarsi al nuovo Locale selezionato.
Questo scenario mostra in tutta la sua chiarezza il modello tipico di gestione del portale basato su portlet: una richiesta da parte dell‘utente scatena l‘esecuzione di logica di una portlet specifica sul server, mentre altre portlet dovranno semplicemente rinviare l‘output al browser in funzione del nuovo stato assunto dal sistema.
La API per questo motivo distingue fra richieste che richiedono l‘esecuzione di business logic sul server (ActionRequest) da quelle che semplicemente sono deputate alla visualizzazione di un output (RenderRequest).
Per onor di sintesi diremo quindi che a fronte di una request da parte dell‘utente il portal server inoltra tale richiesta come action request verso la portlet associata a tale invocazione: successivamente il portal server inoltra la chiamata di tipo render alle altre portlet che dovranno ridisegnare l‘output relativo alla parte di pagina di loro competenza.
Se lo stato del server è cambiato (come nell‘esempio del cambio lingua) tutte le portlet interessate eseguiranno effettivamente l‘operazione di rendering, mentre se non è necessario il portal server invierà al browser il contenuto della area di pagina memorizzato in cache (con una ulteriore forma di ottimizzazione).
Quello che nella servlet è il metodo service(), che consente di rispondere a una qualsiasi richiesta del client, in una portlet viene sdoppiato in due metodi, uno per rispondere a una request di tipo action e l‘altro per visualizzare l‘output della portlet. Per comprendere come tali metodi sino invocati è necessario affrontare con completezza il ciclo di invocazione di una requst, cosa che vedremo fra poco.
Il flusso della chiamata browser-portlet
Il meccanismo con il quale la richiesta viene inoltrata dal browser sulla specifica portlet segue lo schema accennato nel paragrafo precedente. Volendo entrare maggiormente nel dettaglio, vediamo di capire nello specifico quali sono i metodi coinvolti.
Si è detto che quelle che in una servlet sono delle semplici HTTPRequest in questo caso sono rimappate in istanze di ActionRequest (che provocano l‘esecuzione di logica e quindi cambiano lo stato della portlet) e RenderRequest (che servono per visualizzare un output in base allo stato attuale della portlet).
Il portal server determina il tipo di request arrivata dal client: ogni action-request viene inoltrata alla portlet associata a tale URL e successivamente il portal manda una render-request a tutte le portlet della pagina. Da notare che l‘URL di invocazione non può essere generato manualmente (come invece si è soliti fare quando si compone una pagina HTML/JSP in cui si vuole inserire un link da associare a una serlvet), ma deve sempre essere creato dalla portlet tramite un metodo apposito: vedremo di tornare meglio su questo concetto, per adesso si dirà che essendo la portlet responsabile della visualizzazione della propria portlet-window tramite il metodo di rendering, sarà all?interno di tale metodo che si genererà il link per l‘esecuzione della operazione di action.
La API mette a disposizione i seguenti metodi per la gestione delle request:
public void processAction(ActionRequest req, ActionResponse res) throws PortletException, IOException
public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException
Il primo verrà eseguito se alla portlet arriva una ActionRequest e normalmente non deve essere sovrascritto, se non per gestire nuovi stati della portlet o per implementare output alternativi ai vari stati (es. quando in minimized).
Il metodo render() è invece la base per l?implementazione delle operazioni necessarie alla produzione dell‘output della portlet. Il codice che effettivamente verrà eseguito dipende dalla particolare implementazione che si fa della portlet.
In linea teorica si potrebbe pensare di provvedere a implementare l?interfaccia Portlet (se si desidera implementare una qualche funzionalità custom del container), anche se in pratica si preferisce sempre derivare dalla classe GenericPortlet
public abstract class GenericPortlet extends Object implements Portlet, PortletConfig{
...
}
Tale classe, oltre a essere utile perché implementa tutti i dettagli relativi alla esecuzione della portlet, fornisce un flusso predeterminato di gestione della richiesta predefinito e particolarmente utile.
Se si decide di derivare dalla GenericPortlet, per ogni request di tipo render il portal server per prima cosa intercetta tale richiesta con il metodo render(): tale metodo, dopo aver settato il titolo della portlet (operazione che per specifica deve essere eseguita sempre), passerà il controllo al metodo doDispatch():
public void doDispatch(ActionRequest req, ActionResponse res) throws PortletException, IOException
Tale metodo verifica il “window-state” per determinare cosa e come visualizzare: se lo stato non è minimized delega, in funzione del portlet-mode correntemente selezionato per la portlet, la gestione del rendering ai metodi doEdit(), doView() o doHelp() in funzione della attuale modalità che la portlet sta assumendo:
public void doView(RenderRequest req, RenderResponse res) throws PortletException, IOException
public void doEdit(RenderRequest req, RenderResponse res) throws PortletException, IOException
public void doHelp(RenderRequest req, RenderResponse res) throws PortletException, IOException
Oltre a tali metodi il programmatore dispone anche dei classici init() e destroy() utili per gestire risorse esterne in uso nella portlet per tutto il suo ciclo di vita.
La request e la reponse
L‘interfaccia PortletRequest rappresenta la richiesta del client: essa contiene i parametri o i dati di invocazione, le informazioni del client e offre i metodi comuni per gestire la richiesta.
La API mette a disposizione le due derivazioni ActionRequest, passata al metodo processAction() e la RenderRequest, passata al metodo render().
Come accennato in precedenza gli URL di invocazione sono sempre generati dal container: anche volendo non sarebbe possibile “indovinare” l‘indirizzo di action associato alla portlet, perché, oltre al nome, il portal server aggiunge una serie di prefissi tipici del container e della particolare implementazione.
Le portlet possono creare URL impostando parametri che saranno utilizzati al click successivo: tali parametri devono essere sempre consumati nel metodo processAction() dato che non sono propagati alla render request. Se processAction() vuole propagare parametri in modo che possano essere utilizzati in fase di produzione dell?output, questi dovranno essere impostati nella response.
L‘interfaccia PortletResponse è utilizzata dalla portlet per fornire contenuti, comunicare informazioni al container, cambiare lo stato della portlet e per passare informazioni necessarie per comporre la pagina. Anche in questo caso la API fornisce due sottotipi la ActionResponse e la RenderResponse.
I metodi offerti da tali interfacce sono quelli tipici per la gestione dell?outuput: ad esempio per la gestione del MIME type si può utilizzare la coppia di metodi
setContentType(String type)
getResponseType()
dove il secondo restituisce il MIME type corrente fra quelli supportati e dichiarati nel file di configurazione.
Il metodo
getWriter()
serve per ricavare il writer con cui stampare l?output della portlet window.
Prima di procedere con ulteriori approfondimenti sulla programmazione delle portlet ecco un piccolo esempio che fornisce la visione di insieme sul codice necessario per creare una portlet:
package com.mokabyte.portlets;
import java.io.IOException;
import java.io.Writer;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
public class FirstPortlet extends GenericPortlet{
protected void doView(RenderRequest req, RenderResponse res) throws PortletException, IOException {
res.setContentType("text/html");
Writer writer = res.getWriter();
writer.write("Salve mondo, sono la prima portlet del corso sulle portlet");
}
}
Creazione degli URL: come invocare una portlet in modalità action
Per invocare una portlet si deve creare un URL in modo contestuale al container di esecuzione: in genere tale URL (relativo) che la referenzia può essere creato da codice impostando i parametri di invocazione. Sarà il portal container a contestualizzare tale URL.
I metodi che si possono utilizzare sono i seguenti:
public URL createRenderURL()
public URL createActionURL()
public void setParameter(String name, String value)
public void setParameters(java.util.Map params)
Per maggior chiarezza sulla sequenza delle operazioni necessarie per invocare una portlet in modalità action si potrebbe riassumere la invocazione nel seguente schema:
- La portlet crea un ActionURL visualizzato come link o come action di un form HTML
- L‘utente clicca su tale link
- Il portal processa tale request e passa alla portlet la relativa ActionRequest inserendo i parametri.
- La portlet esegue processAction()
Se dopo l‘esecuzione dell‘azione lo stato della portlet deve cambiare (visualizzare altro output, modificare la risposta) allora si mettono parametri in RenderResponse in modo che possano essere utilizzati per tutte le operazioni di rendering sucessive, fino alla successiva invocazione di una ActionRequest.
Vediamo nel dettaglio il codice relativo agli step precedenti: per prima cosa alla prima invocazione della portlet (in modalità di visualizzazione), questa crea il form con il quale invocare l‘action associata alla portlet.
Si faccia attenzione ai parametri passati che verranno poi utilizzati per modificare lo stato della portlet e quindi la risposta:
protected void doEdit(RenderRequest renderRequest,RenderResponse renderResponse) throws PortletException, IOException{
// stampa il form HTML che invocherà la portlet");
writer.write("Portlet Settings
");
writer.write("
}
In questo caso si utilizza una tecnica rudimentale per stampare il form di invocazione (stampandone l?HTML riga per riga): normalmente si usano tecniche più evolute che includono pagine JSP o template di vario tipo.
Successivamente, dopo che l?utente ha cliccato sul form (siamo al punto 3 dello schema precedente), si processa l?azione: i parametri sono passati al rendering per visualizzare il nuovo stato
public void processAction(ActionRequest aRequest, ActionResponse aResponse) throws PortletException, IOException{
//ricava i parametri di action
String newsTitle = actionRequest.getParameter("newsTitle");
String newsBody = actionRequest.getParameter("newsBody");
if (title != null){
//salva il parametro per le operazioni di rendering
actionResponse.setRenderParameter("newsTitle", newsTitle );
}
if (contents != null) {
//salva il parametro per le operazioni di rendering
actionResponse.setRenderParameter("newsBody", newsBody);
}
}
Infine nel rendering finale si stampa il contenuto in base ai parametri di renderRequest:
protected void doView(RenderRequest req, RenderResponse res) throws PortletException, IOException{
// setta il MIME Type
res.setContentType("text/html");
// ricava il writer per poter stampare
Writer writer = res.getWriter();
// ricava i parametri di request inoltrati dalla action
String newsBody = req.getParameter("newsBody");
String newsTitle = req.getParameter("newsTitle");
if (newsBody != null && newsTitle != null){
writer.write("newsBody");
writer.write("newsTitle");
}
else{
//se non è stato impostato il content visualizza un messaggio di default
writer.write("Nessuna messaggio da visualizzare");
}
}
Conclusione
In questo articolo abbiamo affrontato i concetti fonadmentali della programmazione delle portlet. I pochi esempi visti dovrebbero essere sufficienti per comprendere le tecniche di base della API.
Nelle prossime puntate parleremo ulteriormente della programmazione delle portlet e inizieremo a vedere gli aspetti legati al portal server (configurazione, aspetti, performance).
Bibliografia
- JSR 168: Portlet Specification – http://jcp.org/en/jsr/detail?id=168
- Building Portals with the Java Portlet API? by Jeff Linwood and Dave Minter