Liferay Portal Overview

IV parte: Gestire i dati con il Service Builderdi

Dopo aver introdotto i concetti base per sviluppare portlet custom, in questo articolo vedremo come fare qualcosa di più complicato, ma sicuramente più utile e interessante. Spiegheremo i concetti base per creare nel database le nostre tabelle, o meglio, illustreremo come farle creare al portale. Scopriremo come lo sviluppo di gran parte del codice per gestire i dati di queste tabelle sarà a carico del portale.

Service Builder

Il Service Builder [1] altro non è che un generatore di codice. Questa affermazione potrebbe sembrare riduttiva; alla fine dell'articolo capiremo quanto invece questo tool ci faciliti lo sviluppo. Ciò che viene generato dal Service Builder è l'insieme di classi e interfacce che servono per gestire i dati delle nostre tabelle. Prima di vedere come si utilizza, illustriamo brevemente come sono organizzate tra loro queste classi e interfacce.

Diciamo innanzitutto che il codice generato può essere suddiviso in tre layer: model, service e persistence. Nel model rientrano le interfacce e le classi che, come dice la parola, rappresentato il nostro modello, ossia la nostra tabella. Nello strato persistence troviamo le classi che gestiscono la persistenza dei dati, quindi la creazione/cancellazione sul e dal DB, la lettura e l'aggiornamento dei dati. Infine c'è lo strato service, attraverso il quale potremo utilizzare le chiamate verso il persistence; non è infatti possibile fare le chiamate direttamente allo strato persistence. In Figura 1 è schematizzato quanto appena descritto.

Figura 1 - Organizzazione di classi e interfacce.

 

Configurazione

Vediamo ora come si utilizza il Service Builder. Prima di tutto è necessario creare un Plugin Portlet, Gestione Libreria. Dentro questo plugin creiamo due portlet Gestione Autori e Gestione Libri che implementano la classe MVCPortlet. Apriamo liferay-portlet.xml e aggiungiamo queste righe:

content
1.0

e

false

appena sotto per entrambe le portlet. Questo farà sì che queste verranno visualizzate nel pannello di controllo del portale. Immaginiamo, infatti, di creare delle portlet per il back-end.

Se deployamo e avviamo il server, vedremo che nel pannello di controllo troviamo nell'elenco le nostre due portlet che per il momento visualizzano solamente una scritta creata di default dal Plugin SDK.

A questo punto siamo pronti per creare la configurazione necessaria al Service Builder. Quello di cui abbiamo bisogno è un file XML, service.xml, da creare dentro la cartella WEB-INF del nostro Plugin Portlet. In questo file, tramite la sintassi espressa nella DTD, per il momento andremo a dichiarare le nostre tabelle e le colonne di cui sono composte.

"http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd">

       Libreria
      
            
            
            
            
            
            
            
            
            
            
            
            
            
      

      
            
            
            
            
            
            
            
            
            
            
            
            
            
            
                                 mapping-key="authorId"  />
      

Qualche nota

Analizziamo le varie righe:

Qui dichiariamo il package che avranno tutte le classi e le interfacce generate.

Libreria

Questo è il prefisso che sarà usato per i nomi delle tabelle sul database.

Nome dell'entity e della tabella sul database. Se per la tabella vogliamo specificare un nome diverso dobbiamo utilizzare l'attributo table.

local-service="true" remote-service="true"

Dicono al Service Builder di creare le interfacce locali e remote rispettivamente.

Dentro l'elemento entity andremo a specificare tutte le colonne con i rispettivi tipi tramite l'elemento column. Se dentro column mettiamo primary="true", allora quella colonna sarà utilizzata come primary key. Naturalmente è possibile indicare più colonne come primary key.

Attenzione alla riga

Questa non crea effettivamente una colonna books nella tabella Autore. Serve a specificare una relazione 1-n tra la tabella Autore e quella Libro. In questo modo il Service Builder genera automaticamente i metodi per recuperare una lista di Libri dato l'id di un autore, poiche' è quella la colonna che usiamo per la relazione nell'attributo mapping-key.

Naturalmente è possibile esprimere anche relazioni n-n, in quel caso invece di mapping-key si usa l'attributo mapping-table.

Il risultato

Una volta creato il nostro file service.xml non ci resta che invocare il target build-service del nostro ant (figura 2).

Figura 2 - Invocazione del target build-service.

 

Se facciamo refresh del progetto dentro Eclipse, vedremo il risultato dell'operazione appena fatta: dentro la src ci sono ora dei nuovi package e delle nuove classi, mentre dentro la cartella lib sotto WEB-INF c'è il JAR che contiene altre classi e interfacce.

A questo punto facciamo deploy e facciamo ripartire il portale: sarà il portale stesso, in questo momento, a creare le tabelle definite nel file service.xml sul nostro database.

Esempi di applicazione

Vediamo ora come utilizzare le classi appena create e come aggiungere metodi di utility, sempre attraverso il Service Builder. Partiamo dalla view della portlet Gestione Libro. Vogliamo che nella view vengano visualizzati tutti i libri che ci sono sul database nella community in cui ci troviamo, in altre parole tutti i libri il cui groupId è uguale all'id che identifica la community (vedremo come si recupera applicativamente l'id della community corrente). Se guardiamo il file service.xml notiamo che abbiamo creato una colonna groupId

che è proprio quella che ci interessa.

Come facciamo a dire al Service Builder di creare per noi un metodo che, dato questo id, ci restituisca la lista dei libri? Lo facciamo sempre tramite il service.xml aggiungendo semplicemente queste righe subito dopo l'ultimo elemento column:


      

L'attributo name dentro finder specifica il nome del metodo che sarà creato, return-type è il tipo di ritorno del metodo. Dentro finder-column indichiamo il nome della colonna da utilizzare per fare la nostra ricerca.

Ora lanciamo di nuovo il target build-service: a tal proposito, ricordiamo che, in caso di errore del tipo [java] Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version number in .class file è bene effettuare un clean del progetto. Appena l'ant termina, facciamo di nuovo refresh del progetto. Se adesso guardiamo dentro l'interfaccia LibroPersistence, vedremo il metodo findByGroupId(long groupId), e una serie di metodi addizionali che il Service Builder crea automaticamente.

Come abbiamo detto prima, però, non è possibile fare le chiamate direttamente verso lo strato persistence: per questo, se vogliamo utilizzare il finder appena creato, dobbiamo "esporlo" attraverso lo strato service. Per farlo, creiamo un metodo dentro LibroLocalServiceImpl: questa è l'unica classe che possiamo modificare e dove metteremo i nostri metodi custom. Aggiungiamo il seguente metodo:

public List findByGroupId(long groupId) throws SystemException{
       return libroPersistence.findByGroupId(groupId);
}

Lanciamo nuovamente il target build-service. Il Service Builder si accorgerà che abbiamo aggiunto un metodo dentro LibroLocalServiceImpl, la cui firma andrà ad aggiungere nell' interfaccia LibroLocalService. Dentro LibroLocalServiceUtil troviamo invece l'implementazione che non fa altro che invocare il metodo che abbiamo appena scritto. LibroLocalServiceUtil infatti è un wrapper della classe cui abbiamo appena aggiunto un metodo.

Questo giro, apparentemente un po' complicato, ci permetterà di chiamare dalla nostra classe GestioneLibroPortlet il metodo LibroLocalServiceUtil.findByGroupId(groupId)che restituisce una lista di oggetti Libro. Utilizzando l'oggetto SearchContainer mostriamo nella view la lista dei risultati, analogamente a quanto fatto nel precedente articolo con gli utenti.

Inserire i dati

Naturalmente la lista sarà vuota poiche' non abbiamo inserito nessun libro. Per farlo dobbiamo implementare la logica che ci permette di farlo. Prima però è necessario inserire almeno un autore poiche' la tabella libro ha una colonna authorId che referenzia un autore. Vediamo come fare.

Abbiamo bisogno quindi di un form per inserire Nome e Cognome .Questo è il codice JSP che renderizza la form:

<%@include file="/html/init.jsp"%>



      
                                 value="<%=autore.getAuthorId() %>" type="hidden" />
            
             autoFocus="true" size="45" />
            
            
                   
            

      

La prima riga è una direttiva per includere un'altra JSP nella quale abbiamo messo tutti gli import necessari, ed è mostrata parzialmente sotto:

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet" %>
<%@ taglib uri="http://liferay.com/tld/security" prefix="liferay-security" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Calendar" %>
<%@ page import="java.util.Collections" %>
.
.
.
<%@ page import="it.dvel.libreria.model.Libro" %>
<%@ page import="it.dvel.libreria.model.Autore" %>
<%@ page import="javax.portlet.PortletURL" %>


Riprendiamo la JSP con la form e analizziamo le righe più importanti. In
type="it.dvel.libreria.model.Autore" scope="request" />

recuperiamo dalla request  un oggetto tramite la chiave autore. È l'oggetto che rappresenta l'autore che vogliamo inserire: in questo caso è vuoto, poiche' così l'abbiamo creato dentro GestioneAutoriPortlet; al contrario, sarà popolato nel caso in cui vogliamo modificare un autore. Questo ci permette di utilizzare la stessa JSP per inserimento e modifica. Infatti la riga 

value="<%=autore.getAuthorId() %>" type="hidden" />

crea un campo di input di tipo hidden che contiene l'id di un oggetto Autore. Per quanto detto sopra sarà 0 in caso di nuovo inserimento mentre sarà uguale all'id dell'autore sul database in caso di modifica. Il discorso della modifica sarà chiaro più avanti.

Nella riga

utilizziamo una taglib [2] per creare un actionURL che sarà richiamato dal nostro form al momento del submit: l'attributo name contiene il nome del metodo della nostra classe GestioneAutoriPortlet  da invocare. Tutti i tag che invece iniziano per utilizzano la taglib Alloy UI [3] di Liferay per renderizzare i form.

Ora che abbiamo la form dobbiamo fare in modo di visualizzarla nel portale: lo faremo tramite un pulsante dentro la JSP che mostra la lista degli autori: cliccandolo ci verrà mostrata la vista che contiene la form. Basta pochissimo codice:

Questa riga crea l'URL da chiamare quando clicchiamo sul pulsante con la taglib di cui abbiamo parlato sopra. Ovviamente dentro la classe GestioneAutoriPortlet mi aspetto un metodo che si chiama addAutore.

"
onClick="location.href = '<%=addAutoreURL.toString() %>';" />

Questa riga mostra il nostro bottone. Sul click verrà chiamato l'URL che abbiamo creato nella riga precedente. Quella che segue, infine, è l'implementazione del metodo addAutore:

public void addAutore(ActionRequest request, ActionResponse response)
             throws Exception {
       //creiamo un oggeto AutoreImpl vuoto
       AutoreImpl autore = new AutoreImpl();
       //lo mettiamo in request,sarà recuperato nella JSP
       //tramite        request.setAttribute("autore", autore);
       //dichiariamo quale JSP utilizzare per renderizzare
       //il risultato.In questo caso è quella che mostra la form
       response.setRenderParameter("jspPage",
             "/html/gestioneautoriportlet/edit_autore.jsp");
}

In figura 3 vediamo il pulsante aggiunto in pagina.

Figura 3 - Il pulsante aggiunto alla view.

 

Ed ecco il nostro form (figura 4).

Figura 4 - Il form per inserire un autore.

Salvare i dati

Ora manca il codice che salva l'autore sul database, in particolare il metodo updateAutore richiamato al submit. Prima di vederlo. però. completiamo la parte che ci consente di modificare un autore già inserito.Il form c'è già, manca la logica che recupera i dati dal database e popola l'oggetto Autore. Tale logica è racchiusa nel seguente metodo dentro GestioneAutoriPortlet.

public void editAutore(ActionRequest request, ActionResponse response)
       throws Exception {
       //recupero dalla request l'id dell'autore che voglio modificare.
       //Più avanti vedremo dove questo id sarà messo in request.
       String primKey = ParamUtil.getString(request, "resourcePrimKey");
       if (primKey != null) {
             try {
                    //Recupero dal database i dati del mio autore, utlizzando
                    //uno dei metodi generati automaticamente dal
                    //Service Builder
                    Autore autore = AutoreLocalServiceUtil.getAutore(Long
                                  .parseLong(primKey));
                    //metto l'oggetto autore in request
                    request.setAttribute("autore", autore);
                    //setto la JSP da utilizzare
                    response.setRenderParameter("jspPage",
                           "/html/gestioneautoriportlet/edit_autore.jsp");
             } catch (NumberFormatException e) {
                    SessionErrors.add(request, "failed-to-retrieve");
             } catch (PortalException e) {
                    SessionErrors.add(request, "failed-to-retrieve");
             } catch (SystemException e) {
                    SessionErrors.add(request, "failed-to-retrieve");
             }
       }
}

Come dicevamo prima, ora l'oggetto Autore in request non è vuoto, quindi nella JSP del form, grazie a questa riga vista sopra

troveremo i campi popolati, compreso resourcePrimKey, il quale conterrà l'id.

Vediamo ora dove viene invocato il metodo appena descritto. In figura 5 vediamo il particolare del pulsante "Action" mostrato in ogni riga della lista di autori, come si vedeva in figura 3.

Figura 5 - Particolare del menù delle azioni disponibili sugli autori.

 

Quando clicchiamo sul tasto Action si apre un menù che presenta due azioni disponibili: Delete e Edit. In particolare Edit ci permette di recuperare i dati dell'autore selezionato e di mostrarli nella form.

Questo menu viene renderizzato grazie a una JSP inclusa direttamente da codice quando si popola l'oggetto Search Container, nel metodo doView, analogamente a come abbiamo visto nel precedente articolo. L'unica cosa che cambia è proprio la JSP inclusa, che è questa:

<%@include file="/html/init.jsp" %>
<%
ResultRow row = (ResultRow)
       request.getAttribute(WebKeys.SEARCH_CONTAINER_RESULT_ROW);
Autore myAutore = (Autore)row.getObject();
long groupId = themeDisplay.getLayout().getGroupId();
String name = Autore.class.getName();
String primKey = String.valueOf(myAutore.getAuthorId());
%>

      
            
                   
            

                                        url="<%=editURL.toString() %>"/>
      

      
            
                   
            

            
      

Utilizzando un'altra taglib, Liferay UI [4], creare il menù è piuttosto semplice. Il primo pulsante che viene aggiunto è quello per la modifica, il cui URL punterà proprio al metodo editAutore visto sopra, al quale passerà come parametro il resourcePrimKey che utilizzeremo per fare la get dal database dei dati.

Il secondo pulsante serve per la cancellazione, richiama il metodo deleteAutore dentro GestioneAutoriPortlet mostrato qui sotto.

public void deleteAutore(ActionRequest request, ActionResponse response)
             throws Exception {
       //prendiamo dalla request l'id
       long autoreKey = ParamUtil.getLong(request, "resourcePrimKey");
       if (Validator.isNotNull(autoreKey)) {
             //se l'id non è nullo o vuoto invochiamo il metodo
             //per la cancellazione, creato anch'esso dal
             //Service Builder
             AutoreLocalServiceUtil.deleteAutore(autoreKey);
             SessionMessages.add(request, "autore-deleted");
       } else {
             SessionErrors.add(request, "error-deleting");
       }
}

A questo punto per completare la gestione della tabella autori manca solo il metodo che fa l'inserimento o l'aggiornamento dei dati, mostrato qui sotto.

public void updateAutore(ActionRequest request, ActionResponse response)
             throws PortletException {
       Autore autore = null;
       ThemeDisplay themeDisplay = (ThemeDisplay) request
                    .getAttribute(WebKeys.THEME_DISPLAY);
       ArrayList errors = new ArrayList();
       //nel metodo di util       autoreFromRequest andremo
       //semplicemente a recuperare i parametri passati
       //tramite la form
       autore = AutoreUtil.autoreFromRequest(request);
       long idAutore;
       if (autore.getAuthorId() > 0) {
             // se l'id passato è !=0 significa che abbiamo modificato
             //un autore che già esiste sul DB, quindi si tratta di
             //un update 
             try {  //recuperiamo I dati che già si trovano sul DB
                    Autore fromDB = AutoreLocalServiceUtil.getAutore(autore
                                  .getAuthorId());
                    if (fromDB != null
                                  && (autore.getAuthorId() == fromDB.getAuthorId())) {
                           autore.setModifiedDate(new Date());
                           autore.setCreateDate(fromDB.getCreateDate());
                           //facciamo l'update
                           fromDB = AutoreLocalServiceUtil.updateAutore(autore, false);
                           SessionMessages.add(request, "statement-added");
                           idAutore = fromDB.getAuthorId();
                    }
             } catch (PortalException e) {
                    errors.add("failed-to-update");
             } catch (SystemException e) {
                    errors.add("failed-to-update");
             }
       }
       else {
             //se entriamo qui vuol dire che stiamo facendo un nuovo inserimento
             try {
                    //inseriamo
                    idAutore = (AutoreLocalServiceUtil.addAutore(autore.getUserId(),
                                  autore.getName(), autore.getSurname(),
                                  themeDisplay.getScopeGroupId())).getAuthorId();
             } catch (SystemException e) {
                    errors.add("failed-to-add");
             } catch (PortalException e) {
                    errors.add("failed-to-add");
             }
       }
       if (errors.size() > 0) {
             for (String error : errors) {
                    SessionErrors.add(request, error);
             }
             request.setAttribute("autore", autore);
             response.setRenderParameter("jspPage",
                           "/html/gestioneautoriportlet/edit_autore.jsp");
       }
}

La gestione per la tabella Autore è terminata.

Torniamo alla tabella Libro sulla quale abbiamo lasciato in sospeso il discorso inserimento, dal momento che abbiamo detto di aver bisogno di almeno un autore al quale "assegnare" un libro. La logica e l'implementazione per tutte le operazioni sono quasi speculari a quelle viste per l'autore. L'unica differenza riguarda, appunto, l'inserimento. Il form per l'inserimento che vogliamo ottenere è mostrato in figura 6.

Figura 6 - Form per l'inserimento di un libro.

 

Come si vede c'è una select che è popolata con la lista degli autori. Nella nostra classe GestioneLibriPortlet scriviamo questo metodo:

private void addAutoriListToRequest(ActionRequest request)throws Exception {
       ThemeDisplay themeDisplay =
             (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY);
       //recuperiamo lo scope della community
       long groupId = themeDisplay.getScopeGroupId();
       //leggiamo dal DB tutti gli autori inseriti per la community corrente
       List autoriList = AutoreLocalServiceUtil.findByGroupId(groupId);
       //mettiamo in request la lista appena recuperata
       request.setAttribute("autoriList", autoriList);
}

Questo metodo sarà chiamato da addLibro e editLibro. Qui sotto vediamo la JSP che renderizza la form.

<%@include file="/html/init.jsp"%>

type="java.util.List" scope="request" />




      
                                 type="hidden" />
            
                                 autoFocus="true" size="45" />
            
             <%
                    for (int i = 0; i < autoriList.size(); i++) {
                           Autore autore=(Autore)autoriList.get(i);
             %>
                    "
                    selected="<%=autore.getAuthorId()==libro.getAuthorId() %>"
                           value="<%= autore.getAuthorId() %>" />
             <%
                    }
             %>
            

            
                   
            

      

Soffermiamoci sul pezzo di codice che popola la tendina. Utilizziamo ancora le taglib Alloy UI. Per generare le option cicliamo sulla lista degli autori che è stata messa in request con il  metodo addAutoriListToRequest di sopra. In particolare questa istruzione

selected="<%=autore.getAuthorId()==libro.getAuthorId() %>"

fa sì che, quando vogliamo modificare un libro, l'elemento della tendina selezionato di default è proprio quello corrispondente all'autore per il libro.

Se adesso inseriamo un libro, questo avrà anche un autore!

Conclusioni

In questo articolo abbiamo descritto un tool fondamentale nello sviluppo di portlet per Liferay, il Service Builder. È un generatore di codice che produce, organizzate in una precisa struttura in livelli, le classi e le interfacce per gestire i dati su tabelle custom che creiamo sul database. I metodi base che crea sono quelli per inserimento, lettura, cancellazione e aggiornamento.

Tutto quello di cui abbiamo bisogno è un file xml dove , con un preciso DTD, dichiariamo le tabelle che vogliamo creare e le rispettive colonne. Sarà il portale a creare gli script SQL e a eseguirli sul nostro database la prima volta che lo facciamo ripartire.

Abbiamo visto poi come sia possibile affiancare dei metodi "finder" custom ai metodi di default. Nell'articolo sono serviti per recuperare libri e autori con un determinato community id; naturalmente non è l'unica applicazione possibile. Si possono ad esempio creare dei "finder" per fare delle ricerche sui campi titolo, nome o cognome. Inoltre è possibile, sempre dentro l'XML, specificare l'ordinamento dei dati dichiarando su quale colonna ordinare, e se in modo ascendente o discendente.

Abbiamo anche visto come aggiungere della logica custom costruita sui metodi che già esistono: creando dei metodi dentro la classe LocalServiceImpl.java, il ServiceBuilder, una volta lanciato, crea dentro l'interfaccia LocalService.java un metodo con la stessa firma del nostro, e dentro LocalServiceUtil.java la sua implementazione. Questa classe in pratica altro non è che un wrapper per la LocalServiceImpl.java che contiene la nostra implementazione custom.

Con quest'approccio è possibile quindi costruire query complesse per applicazioni più vaste e articolate risparmiando, oltretutto, una notevole quantità di tempo.

Riferimenti

[1] Service Builder

http://www.liferay.com/community/wiki/-/wiki/Main/Service+Builder

 

[2] JSR 286 tag library

http://www.oracle.com/technetwork/java/jsr286-2-141964.html#Tag_Library

 

[3] Liferay Alloy UI

http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Alloy+UI+Forms+(aui)

 

[4] Liferay UI

http://www.liferay.com/community/wiki/-/wiki/Main/Liferay+UI+Taglib

 

[5] Service Builder tips

http://blog.d-vel.com/web/blog/home/-/blogs/query-su-colonne-unique:-come-configurare-il-servicebuilder

 

Condividi

Pubblicato nel numero
161 aprile 2011
Lorenza Amato è nata a Chieti nel 1982. Si è laureata in Informatica presso l‘Università degli studi di Bologna. Dal 2009 svolge, presso la D‘vel Snc, l‘attività di sviluppatrice software in ambiente Java. In particolare si occupa di sviluppo di progetti per Liferay Portal Server.
Articoli nella stessa serie
Ti potrebbe interessare anche