Portlet API

III parte: la programmazione delle portletdi

Prosegue la trattazione della programmazione delle portlet. Questo mese parliamo di ciclo di vita, di gestione della sessione utilizzata sia come repository che come strumento di interconnessione fra portlet differenti.

Il ciclo di vita delle portlet

Il ciclo di vita di una portlet è piuttosto semplice e per molti aspetti analogo al corrispettivo ciclo delle servlet. Le varie fasi sono individuate dai tre seguenti stati:

  • caricamento delle classi e inizializzazione
  • gestione della request
  • distruzione della portlet

di seguito sono analizzati i dettagli di questi tre punti.

Fase 1: caricamento delle classi e inizializzazione

Il meccanismo di base del caricamento di una portlet non è dissimile da quello adottato da ogni altra applicazione Java EE. Il container procede secondo le regole (e le priorità ) dell‘application server sul caricamento delle classi e delle librerie presenti all‘interno dell‘archivio di deploy della applicazione.
La classe principale della portlet application e le classi di supporto sono caricate dal medesimo classloader in modo da condividere lo stesso spazio di indirizzamento.
La inizializzazione della portlet avviene al momento dello startup della applicazione o del portal server (se specificato il parametro di autostart) o in alternativa al momento della prima invocazione da parte del client.
In questa fase i valori delle variabili sono impostati ai loro valori di default: la specifica impone nella portlet la presenza di un costruttore di default, anche se il buon senso ci dice (come nel caso delle servlet) di non usare questo metodo per la inizializzazione della portlet ma piuttosto il metodo init() dove è disponibile il contesto di configurazione come parametro di invocazione:

public void init(PortletConfig config) throws PortletException, UnavailableException {
super.init(config);
String initParam=config.getInitParameter();
}

Analogamente al caso delle servlet fino a che il metodo init() non termina la sua esecuzione la portlet non è disponibile per la esecuzione: per questo motivo è bene non imporre lunghe procedure di inizializzazione cosଠcome è un errore invocare nel metodo init() metodi che necessitino della portlet in uno stato "vivo e funzionante".

Eccezioni durante la fase di inizializzazione

Durante la fase di inizializzazione una portlet può andare incontro a problemi di varia natura che ne possono pregiudicare o meno il funzionamento successivo; si possono verificare problemi temporanei (che con una qualche operazioni di recovery possono essere risolti agilmente) o incidenti definitivi (manca la connessione al database, un importante file di configurazione non viene trovato o un altro grave problema che pregiudica l‘esecuzione della portlet). Il programmatore può notificare al portal server questi due casi lanciando le eccezioni PortletException o UnavalaibleException secondo il seguente schema:

throws new PortletException("Messaggio")
  • il problema è provvisorio e il container può tentare di riattivare la portlet in seguito

throws new UnavalaibleException("Messaggio") 
  • il problema è grave e non si deve riattivare la portlet

throws new UnavalaibleException("Messaggio", n)
  • il problema è grave ma risolvibile: in questo caso si comunica al portlet container che può essere tentata una successiva inizializzazione della portlet fra n secondi

throws new UnavalaibleException("Messaggio", 0)
  • in questo caso si ipotizza un problema non grave per il quale però non è possibile determinare quando potrà  essere riattivata la portlet. Il container tenterà  nuovamente la inizializzazione dopo una pausa arbitrariamente lunga.

Fase 2: gestione della request

La request eseguita dal client sul portale viene tradotta in una RenderRequest o ActionRequest a seconda della tipologia di invocazione: in un caso o nell‘altro il portale invoca il metodo render() o processAction() per rispondere alla richiesta, ma nel caso in cui sia attivo un meccanismo di cache, la ri-renderizzazione non viene forzata.
Se la request è di tipo Action, viene invocato il metodo processAction() al termine del quale il container eseguirà  il refresh dell‘ouput di tutte le portlet della pagina (a meno di cache) senza che sia garantito nessun ordine nella invocazione del metodo render().
Ã? bene considerare che il portal deve garantire un efficiente livello di servizio a tutti i client per tutte le portlet basato su un meccanismo multithread la cui gestione viene eseguita in modo più agile e snello proprio eliminando ogni vincolo di priorità  e sincronia sulla esecuzione di tali thread.

Fase 3: distruzione della portlet

Il portlet container distrugge la porlet quando ritiene non più necessario il suo utilizzo nel container: questa decisione è presa se non ci sono più richieste in corso (e per un qualche motivo si deve fare shutdown della applicazione), quando tutti i thread di rendering o di action hanno terminato la loro esecuzione, oppure quando il processo di inizializzazione si è concluso.
Da notare che il container non è tenuto a mantenere indefinitamente in vita una portlet, ma per motivi vari può procedere alla sua distruzione e successiva inizializzazione (in genere operazione effettuata se la carenza di risorse impone, ad esempio, di ottimizzare l‘uso della memoria).
In ogni caso la specifica garantisce che, prima della completa distruzione della portlet, verrà  invocato il metodo destroy().

Portlet e pagine JSP

Per quanto concerne la procedura di produzione e invio della risposta verso il browser, una portlet utilizza un meccanismo del tutto analogo a quello utilizzato dalle serlvet: per produrre il contenuto la portlet è costretta a stampare riga per riga la pagina HTML, tecnica certamente scomoda e poco potente.

public void doView(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
String format = "EEE d MMM yyyy, hh:mm:ss aaa";
SimpleDateFormat df = new SimpleDateFormat(format);
writer.write("

Ora e Data

");
writer.write(df.format(new Date()).toString());
writer.write("
Clicca qui per invocare la portlet");
}

In alternativa una portlet può includere una pagina JSP, inviando al client il risultato dell‘elaborazione di tale documento.
Ã? molto importante che il programmatore tenga ben presente il fatto che una portlet, in conseguenza della architettura complessiva della portlet API, non può passare il controllo a un‘altra risorsa per stampare l‘output utente (ovvero non può eseguire una redirect/forward) ma deve sempre eseguire una include dell‘output di risorse esterne e inviarlo lei personalmente al client.
La portlet quindi si comporta da controller: dopo aver processato la request (in accordo con il ciclo di vita della portlet (metodo render() -> doView() etc.) passa il controllo alla pagina JSP che esegue il rendering utilizzando oggetti di sessione/request e cosଠvia. La portlet deve sempre mantenere il controllo e non può eseguire forward o redirect a servlet o JSP.
Solo in questo modo è infatti garantito il rispetto del ciclo di vita della portlet e la corretta invocazione dei metodi render-processAction.
L‘oggetto che permette di includere una risorsa (servlet JSP) nell‘output della portlet è il PortletRequestDispatcher, il quale inoltra request e response alla servlet/JSP. Tale componente è ricavabile tramite il PortletContext tramite i due metodi:

public PortletRequestDispatcher getNamedDispatcher(java.lang.String name)
public PortletRequestDispatcher getDispatcher(java.lang.String path)

La differenza fra i due consiste nella possibilità  di ottenere una risorsa per nome (esempio utilizzando il nome della servlet) oppure per path relativo (il filename della JSP contestualizzato nella web application o l‘URL di invocazione della servlet).
L‘oggetto in questione è simile alla RequestDispatcher di javax.servlet, anche se come detto non può eseguire forward, non può passare il controllo a servlet. Il solo metodo permesso è include:

public void include(RenderRequest request, RenderResponse response) 
throws PortletException, IOException

Il suo utilizzo è veramente molto semplice:

protected void doView(RenderRequest request, RenderResponse response) 
throws PortletException, IOException {
response.setContentType("text/html");
PortletContext portletContext = getPortletContext();
PortletRequestDispatcher prd;
prd = portletContext.getRequestDispatcher("userFile.jsp");
prd.include(request, response);
}

L‘invocazione permette di passare dei parametri secondo lo schema degli URL (tecnica non possibile se si usa NamedDispatcher):

prd = portletContext.getRequestDispatcher("userFile.jsp?userId="+userIid);

Per quello che si è appena detto poco sopra, non è possibile includere l‘output di altre portlet con il RequestDispatcher: il meccanismo infatti non permette di passare il controllo alla risorsa invocata e se volessimo ottenere l‘output della portlet dovremmo passarle il controllo. Un modo per ovviare a questa limitazione è l‘invocazione diretta dei metodi della portlet.
Da notare che al momento (API 1.0) una portlet deve sempre fornire output testuale: se la JSP/servlet produce output binario (per esempio PDF, ZIP) si deve necessariamente produrre una pagina con il link della risorsa da scaricare; non si tenti di modificare il content-type della servlet. Da quanto riportato nelle specifiche preliminari, nella prossima release sarà  eliminata questa restrizione.

L‘inclusione di una pagina JSP/servlet nella porlet apre a nuovi scenari fra cui anche la gestione delle eccezioni prodotte dalla pagina stessa. Ogni eccezione prodotta dalla JSP/servlet viene inoltrata alla servlet: se si tratta di una IOException verrà  passata alla portlet, mentre ogni altra eccezione verrà  wrappata come PortletException (queste sono le uniche due eccezioni rilanciate da una servlet/JSP). A tal proposito si deve sempre tenere a mente che la portlet lavora a un livello di astrazione maggiore rispetto alla servlet che vi è inclusa: quest‘ultima ha un accesso limitato alle risorse di request/response e per questo molti metodi non eseguono operazioni o restituiscono valori null.
Ecco alcuni esempi dai quali si evince come alcuni metodi ritornino valori non significativi mentre altri restituiscono il valore della portlet che funge da wrapper della serlvet

getCharacterEncoding()
  • restituisce null

getContextPath() 
  • restituisce il context path della portlet

getContentLength() 
  • restituisce 0

getRealPath() 
  • restituisce null

getProtocol() 
  • restituisce null

getRemoteAddress() 
  • restituisce null

getRequestUrl() 
  • restituisce null

getParameterMap() 
  • restituisce una map con le coppie nomi/valori passati alla portlet.

La portlet converte request/response per la servlet secondo lo schema:

  • RenderResponse diventa HttpServletResponse
  • RenderRequest diventa HttpServletRequest

I parametri di RenderRequest sono disponibili alla serlvet come parametri di HttpServletRequest (i nomi sono conservati). Stesso discorso per gli attributi di request.

Gestione della sessione

Una portlet può tenere traccia della sessione utente tramite l‘oggetto PortletSession (la cui gestione è a carico del portal server) ricavabile dalla portlet request e analogo alla HttpSession usata nelle servlet.
Si deve tenere a mente che, al momento (Portlet API 1.0), la sessione non è condivisa fra le portlet applications cosa che per adesso limita la interportelet communication, anche se questo garantisce comunque un miglior livello di isolamento e sicurezza.
Nell‘ambito di una sessione, la Portlet API mette a disposizione due scope in cui memorizzare oggetti: il portlet scope e l‘application scope. Riguardo al primo scope

  • è pensato per memorizzare dati di una portlet
  • è specifico per ogni istanza di portlet
  • ogni attributo viene inserito in sessione con un prefisso specifico per la istanza della portlet
  • il namespace può essere ricavato da un‘altra portlet eseguendo uno scan di tutti gli attributi in sessione
  • consente di evitare sovra scritture

L‘altro, l‘application scope invece ha le seguenti caratteristiche:

  • scope globale a livello di applicazione
  • disponibile in lettura e scrittura a tutte le portlet della applicazione

Sulla sessione sono possibili diverse operazioni grazie ai seguenti metodi:

public java.lang.Object getAttribute(String name, int scope)
public java.util.Enumeration getAttributeNames()
public java.util.Enumeration getAttributeNames(int scope)
public java.lang.Object getAttribute(String name)
public void removeAttribute(String name)
public void removeAttribute(String name, int scope)
public void setAttribute(String name, Object value)
public void setAttribute(String name, Object value, int scope)

Eseguendo un parallelo fra una servlet e una portlet, il concetto di sessione utente è equivalente: nelle portlet è mantenuta in PortletSession mentre nelle serlvet è mantenuta nell‘oggetto HttpSession. I due wrapper condividono gli oggetti memorizzati con gli stessi nomi. Una portlet può condividere oggetti con le servlet ponendoli in scope application.
Gli oggetti posti in portlet application non possono essere acceduti dalla serlvet non per restrizioni di sicurezza ma piuttosto per la mancanza di una politica sui nomi standard: la servlet non può ricavare il namespace della portlet (assegnato dal portal container). La regola di naming del name space varia da container e da installazione (il programmatore non deve farci affidamento).

La sessione e la interportlet communication

La sessione può essere utilizzata come meccanismo di comunicazione interportlet all‘interno della stessa applicazione, e anzi nella API 1.0 è l‘unico mezzo standard disponibile.
Si deve fare comunque attenzione a porre gli attributi in scope application e disabilitare la portlet-cache: il cambiamento di un attributo di sessione deve essere eseguito nella action request per garantire che le altre portlet usino la modifica durante il render.
A tal proposito, come più volte detto, la specifica non garantisce l‘ordine di rendering delle portlet nella pagina e quindi l‘utilizzo di attributi in sessione non deve essere utilizzato come meccanismo di sincronizzazione fra portlet.
Ecco un esempio che mostra come tale tecnica può essere utilizzata per passare un valore da una portlet a un‘altra (nella stessa applicazione). Nella Portlet A si potrebbe scrivere:

public void processAction(ActionRequest request, ActionResponse response) 
throws PortletException, IOException{
PortletSession session = request.getPortletSession(true);
String uname = request.getParameter("uname");
session.setAttribute("uame", uname, PortletSession.APPLICATION_SCOPE);
}

mentre nella Portlet B (che deve ricevere la modifica del valore tramite sessione):

public void doView(RenderRequest request, RenderResponse response) 
throws PortletException, IOException{
response.setContentType("text/html");
Writer writer = response.getWriter();
PortletSession session = request.getPortletSession(true);
String uname;
uname=(String)session.getAttribute("uname", PortletSession.APPLICATION_SCOPE);
// usa la variabile se diversa da null per stampare l‘output della portlet
}

Conclusioni

Questo mese la trattazione delle portlet termina qui. Nel prossimo articolo inizieremo a vedere come effettuare il deploy e utilizzare una portlet (quindi ad esempio come includere il risultato di una portlet in una pagina JSP tramite custom tag). Questo sarà  il preludio per una trattazione pratica dedicata alla installazione di portlet dentro un portal server.

Condividi

Pubblicato nel numero
120 luglio 2007
Giovanni Puliti lavora come consulente nel settore dell’IT da oltre 20 anni. Nel 1996, insieme ad altri collaboratori crea MokaByte, la prima rivista italiana web dedicata a Java. Da allora ha svolto attività di formazione e consulenza su tecnologie JavaEE. Autore di numerosi articoli pubblicate sia su MokaByte.it che su…
Articoli nella stessa serie
  • Portlet API
    II parte: la programmazione delle portlet
  • Portlet API
    IV parte: strumenti a corredo e prossima release
Ti potrebbe interessare anche