Spesso non viene fatta distinzione tra un sito Web ed un portale Web ed i due nomi vengono usati intercambiabilmente per riferire l'uno o l'altro. Cerchiamo di capire la differenza tra i due:
- un sito è composto da una serie di pagine (generalmente non tantissime) che presentano lo stesso tema di fondo;
- un portale è composto da diversi tipi di argomenti e un numero elevato di pagine, inoltre consente di avere una vista personalizzata per ogni utente che lo visita in base alle sue scelte (sia che sia lui a scegliere, sia che sia un sistema "intelligente" a farlo per lui).
Soprattutto negli ultimi anni c'è sempre più la tendenza a rendere personalizzabile un portale proprio per rendere più facile pubblicizzare, e quindi vendere, i prodotti che meglio si adattano alle preferenze dell'utente. Mettiamoci poi la possibilità della potenza di tecniche come AJAX ed abbiamo pagine Web che davvero sembrano applicazioni desktop, con la possibilità di spostare le finestre dove risiedono le informazioni, aggiungerne di nuove, ridurle a icona...
Ognuno di questi elementi è noto (in particolare in ambiente Java) come Portlet, un componente (Java based) che segue determinate caratteristiche per poterla sviluppare (e relativo ciclo di vita) indipendente dalle altre. In pratica si tratta di una singola applicazione che ha bisogno di un container per poter essere eseguita per sviluppare qualche requisito di logica.
L'immagine che vediamo rappresenta bene l'idea di un portale costruito attraverso portlet. L'utente finale costruisce una pagina per aggregazione dei singoli elementi, avendo come risultato finale una pagina Web configurabile ed adattabile alle volontà dell'utente. Dal punto di vista tecnologico vedremo che la rappresentazione finale sarà affidata al portlet container il cui compito è di seguire le azioni dell'utente (link, form, javascript, ecc), eseguire la logica di business di tutte le portlet (programmate dallo sviluppatore di portlet) e aggregare il risultato proveniente da ognuna di esse in un pannello tipo quello dell'immagine.
Possiamo immaginare allora un portale con una portlet che possiamo aggiungere con le previsioni meteo della nostra città, uno con le notizie sportive, uno con il grafico delle nostre azioni e così via.
Portlet Container
Secondo quanto scritto ogni pagina sarà una composizione di singoli componenti ognuno sviluppato indipendentemente dall'altro. Ovviamente ci sono delle funzionalità generali (come per esempio l'autenticazione) che verranno fornite dal container.
Il container è un elemento molto importante, per lo sviluppatore di portlet, in quanto è quello che fa tutto il lavoro finale di aggregazione e presentazione dei contenuti. Un po' come fa il servlet container che gestisce il ciclo di vita del singolo componente in base a come la portlet è stata sviluppata.
Il ciclo di vita di una servlet viene illustrato nel diagramma che segue:
Prima di vedere il flusso generale, discutiamo l'ultima colonna, dove abbiamo i metodi principali di una portlet. Fondamentalmente ve ne sono due, init()
e destroy()
che vengono richiamati all'inizio e alla fine (sempre dal portlet container) in maniera analoga a quanto accade con le servlet in base ai principi di ottimizzazione decisi dal container.
Il flusso di esecuzione è abbastanza semplice da capire, soprattutto per chi ha un background di sviluppo Web in ambiente Java servlet/JSP. L'idea è quella di avere diverse action che avviano un metodo (process action) in cui viene definita la logica di controllo. Attraverso essa possiamo rindirizzare il processo di render, che è il passo che segue. Il processo di render produce la vista finale della portlet (seguendo il flusso di logica di controllo nel process action).
Ogni singola portlet è vista come una singola mini applicazione che verrà inserita nel contesto di pagina, quindi, il ciclo di vita si riferisce ad essa in quanto singola unità. Il prodotto dell'interazione con l'utente (attraverso i metodi Process Action e Render) produrrà, non una nuova pagina HTML, ma uno spezzone di codice HTML che verrà decorato dal portlet container e inserito nel contesto di pagina. Il caso in questione illustra il flusso di una singola portlet ma, in realtà, dovrebbe essere replicato per quante portlet sono presenti nella pagina di rendering finale.
Il portlet container, quindi, riveste un ruolo centrale ma (almeno in teoria) non influenza il processo di produzione della portlet. Questo perché esiste uno standard (JSR 168) a cui ogni portlet container deve aderire affinché tutte le portlet sviluppate secondo standard possano funzionare correttamente senza problemi di compatibilità.
Lo standard citato precedentemente si riferisce alla specifica Portlet 1, in realtà da qualche tempo è disponibile la specifica Portlet 2 (JSR 286) che al momento in cui scrivo è supportata solo da Liferay. In questo articolo ci dedicheremo esclusivamente alle caratteristiche esposte nella specifica uno della tecnologia, in avanti riprenderemo il discorso della specifica due in quanto essa presenta caratteristiche molto interessanti come la comunicazione inter portlet (argomento molto sentito dalla comunità di sviluppatori).
Liferay è un implementazione opensource di un portlet container che ci offre tutti i servizi di un portal manager. Esistono diverse implementazioni open source e commerciali ma questo è al momento il prodotto che mostra il maggior numero di potenzialità. Per correttezza dico subito che ci sono un po' di portal manager open source che potrete utilizzare al posto di Liferay come, per esempio, Jboss Portal One, Jetspeed 2, Apache Pluto o commerciali come IBM Web Portal, Sap Portal, Oracle AS Portal, Vignette, ecc.
Come potete vedere il mercato è attivissimo su questo fronte proprio perché attraverso l'uso di un portal manager si possono risolvere tanti problemi che si incontrano nella manutenzione di un portale (o di molti portali).
Ognuna delle implementazioni citate presenta migliori o peggiori caratteristiche e aspetti customizzabili. Quasi tutti hanno funzioni di ACL (Access Control List) e di SEO (Search Engine Optimization), ma le possibilità maggiori sono senz'altro quelle di poter applicare temi e "look and feel" in maniera dinamica e totalmente personalizzabile in modo da avere alla fine un sito Web che abbia lo stesso aspetto che possa essere modificato indipendentemente da un unico punto di accesso.
Ritornando a Liferay, vediamo che questo si presenta come una Web application che può essere installata su qualsiasi servlet container (Tomcat, Jetty, ecc), dopodiché, dal pannello di amministrazione avrete la possibilità di gestire le tante funzionalità con cui già viene fornito. Una volta creata una portlet potremo installarla sul portal manager e renderla disponibile ai suoi utenti.
Sviluppo di portlet e relativo esempio
Secondo quanto abbiamo scritto in precedenza, sviluppare una portlet equivale dal punto di vista dell'attività di codifica a sviluppare una Web application indipendente, seguendo però alcune regole (estendere una GenericPortlet, definire un file di configurazione) e convenzioni (generare un pezzo di HTML).
Come primo semplice esempio sviluppiamo una portlet (secondo la specifica 1.0) in modo che essa possa essere indipendente e quindi installabile su qualsiasi portal manager. Quindi quello che dovremo fare sarà:
- Creare un progetto e importare le librerie necessarie;
- Creare una classe che esegua la logica della portlet;
- Creare una serie di descrittori XML.
Come primo punto, dunque, prepariamo un ambiente di sviluppo tale importando la libreria portal.jar che generalmente si trova insieme all'installazione del portal manager (come ad esempio Liferay). Il progetto dovrà avere le caratteristiche di un progetto Web (quindi se utilizzate eclipse, create un Web dynamic project) con cartella Web-Inf e descrittore web.xml.
Una volta preparato l'ambiente per accogliere il progetto, creiamo una classe che estenda da GenericPortlet e che contenga i metodi del ciclo di vita che intendiamo sviluppare:
Package it.html.portlet;
..//
public class HelloWorldPortlet extends GenericPortlet{
protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
response.setContentType("text/html");
response.getWriter().println("Hello World");
}
}
L'unico metodo che in questo esempio andiamo a ridefinire è il metodo doView()
che si occupa di definire la visualizzazione del testo "Hello World" nella portlet che aggiungeremo visualmente ad una pagina Web del nostro portal manager.
<portlet-app
xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
version="1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
http://java.sun.com/
xml/ns/portlet/portlet-app_1_0.xsd">
<portlet>
<description>PrimaPortlet</description>
<portlet-name>Hello World</portlet-name>
<display-name>Hello World</display-name>
<portlet-class>it.html.portlet.HelloWorldPortlet</portlet-class>
<expiration-cache>-1</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
</supports>
<supported-locale>en</supported-locale>
<portlet-info>
<title>Hello World</title>
<short-title>Hello World</short-title>
<keywords>Hello world</keywords>
</portlet-info>
</portlet>
</portlet-app>
Ovviamente abbiamo anche necessità di definire un descrittore (portlet.xml), dove definiamo la descrizione della portlet e la classe che deve eseguirne la logica. Esistono altri tag, che utilizzeremo per migliorare il deploy della nostra portlet (come cache e informazioni per il search della portlet), che tralascio al vostro appofondimento.
Quello che è interessante è approfondire il discorso delle modalità. In questo primo esempio ci siamo limitati ad utilizzare una portlet con un solo "mode", il view, cioè la visualizzazione. Per rendere la portlet piú interessante possiamo pensare di configurarla secondo i gusti dell'utente. In questo caso entra in gioco la modalità edit, dove l'utente può entrare per inserire parametri di preferenza che verranno riportati nella modalità view.
Vediamo la portlet all'opera installandola sul portlet container. Per fare ciò è necessario creare un pacchetto war (web archive), di quanto fatto finora, e installarlo sul Web server su cui si sta eseguendo il Portal Manager. Ogni Portal Manager ha il proprio modo per effettuare il deploy di war; se utilizzate Liferay, attraverso il plugin installer, potrete selezionare l'archivio e caricarlo. Questo si installerà per "hot deploy" e dopo qualche secondo potrete utilizzare la vostra portlet all'interno delle nuove pagine.
Accanto al modo "view", e al modo "edit", la specifica definisce il modo "help", che viene utilizzato come guida o aiuto. In realtà, vista la possibilità di creare modi "custom", sarà possibile pensare altri modi in base alle vostre esigenze.
Ritornando al modo "edit" ci rifacciamo al tipico esempio della portlet delle previsioni meteo per comprenderne l'utilità in un contesto reale. Attraverso la modalità edit, l'utente potrà configurare le proprie preferenze, definendo la città di cui vuole ricevere informazioni meteo. Il nostro caso è più semplice e vediamo di definire un parametro in cui registrare il nickname dell'utente così da personalizzare il saluto. A tal proposito, come prima cosa, aggiungiamo il modo edit al descrittore XML, come modalità supportata:
...
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
<portlet-mode>EDIT</portlet-mode>
</supports>
...
Aggiungiamo anche il metodo che viene richiamato quando entriamo nel modo edit, il doEdit()
, la cui logica è quella di mostrare un form di inserimento. Per questo scopo ci faremo aiutare da una pagina JSP, che gestisce la presentazione del form (in modo da vedere anche un approccio misto portlet/jsp).
protected void doEdit(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher("/editMode.jsp");
dispatcher.include(request, response);
}
Attraverso la procedura di include, deleghiamo la rappresentazione finale alla pagina editMode.jsp, che vediamo di seguito:
<%@ page language="java"%>
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<portlet:defineObjects/>
<form action='<portlet:actionURL/>' method="POST">
<table>
<tr>
<td style="width:150px;">Insert nickname</td>
<td>
<input type="text" name="nickname"/>
<input type="submit" value="GO"/>
</td>
</tr>
</table></form>
L'assenza dei tag HTML, head, body, ecc. non è dovuta ad una dimenticanza, ma è opportuna, in quanto, come abbiamo imparato in precedenza, questo codice HTML andrà dentro una struttura di pagina (un tema) che già li contiene.
Come vediamo il form si prende carico di mostrare un campo (nickname) e di processare l'azione attraverso un custom tag, che serve per poter gestire dinamicamente la URL della portlet stessa, generando, a tempo di esecuzione, l'URL target esatto. Esso può contenere parametri come la action da eseguire, o la modalità della portlet (a schermo intero, ridotta, ...), ma per questo primo esempio va bene definirla semplice senza alcun altro elemento.
Cosa accade nel momento in cui effettuiamo il submit? Come abbiamo visto, nel ciclo di vita della portlet ciò che accade è generare un "processAction" nella portlet di destinazione e quindi eseguire la logica in esso contenuta. Per completare la portlet, quindi, dobbiamo scrivere il metodo che gestisca la logica.
L'idea in questo caso è registrare il parametro nella sessione (o in un'altra struttura dati persistente nota come preferences che utilizzeremo noi) e riutilizzarlo in modalità view (quindi dovremo mettere mano anche al metodo view).
public void processAction(ActionRequest actionRequest,ActionResponse actionResponse) throws PortletException, IOException {
String param = actionRequest.getParameter("nickname");
System.out.println("Your nick is "+param);
actionRequest.getPreferences().setValue("nick", param);
//REMEMBER TO SAVE!!
actionRequest.getPreferences().store();
}
...
Attraverso gli oggetti request e response effettuiamo le operazioni di recupero del parametro e salvataggio in questa struttura dati (simile ad una session ma persistente). Il salvataggio deve essere fatto esplicitamente, altrimenti perdiamo le informazioni inserite.
Il metodo di visualizzazione cambierà opportunamente:
..//
protected void doView(RenderRequest renderRequest, RenderResponse renderResponse)throws PortletException,IOException{
..//
String param = (String) renderRequest.getPreferences().getValue("nickname",null);
if (param == null){
renderResponse.getWriter().println("Hello World. Enter edit mode to change the greetings");
}else{
renderResponse.getWriter().println("Hello "+param);
}
..//
Reinstallate l'applicazione ed entrate nella modalità edit per poter cambiare le preferenze. Esse sono relative ad ogni utente e persistenti sulla base di dati, quindi ognuno avrà una portlet con il suo proprio nome anche a sessione scaduta.
In questo articolo abbiamo discusso dello sviluppo di una portlet attraverso l'estensione dei metodi che gestiscono il ciclo di vita dell'applicazione. Piú interessante è la possibilità data dalla specifica 2 delle portlet (ancora non supportata da tutti i portlet container) che permette di gestire il ciclo di vita della singola portlet (o interportlet) attraverso l'uso di annotazioni, il che rende lo sviluppo piú rapido e preciso.