In questo articolo cominciamo ad affrontare un interessante esempio di Professional Open Source, presentando il porting di una webapp GWT in una portlet Liferay. Ci concentreremo in questo numero su alcuni aspetti generali e sui primi passi concreti da effettuare, completando nel prossimo numero la nostra trattazione.
Introduzione
Ultimamente per lavoro ho dovuto distogliere l’attenzione dagli aspetti a me cari e più meditativi della semantica e dello sviluppo di interfacce GWT [MOKA1] per dedicarmi ad attività di consulenza su Liferay. Liferay [LIFERAY], per chi non lo sapesse, è una piattaforma open source per lo sviluppo di portali enterprise fortemente orientata al social networking (per la serie Facebook docet) e alla collaboration. Elemento chiave della struttura di Liferay è la portlet: in realtà non sono ancora certo che sia femminile… ma a me suona meglio così. Una portlet non è altro che una applicazione web (deployabile con un WAR) arricchita di alcuni specifici deployment descriptor che vive in maniera autonoma all’interno di un Portlet Container e può interagire o meno con le altre portlet. Esistono due specifiche standard per l’implementazione delle portlet (JSR168, JSR286) che permettono di sviluppare portlet indipendentemente dalla piattaforma di portale. Infatti è compito del portlet container di implementare i servizi e l’ambiente standard necessario alla portlet per il suo ciclo di vita. Dal punto di vista dell’interfaccia, una portlet si presenta come una specie di finestra (con tanto di pulsanti di maximize e minimize e configurazione) contenuta in una pagina di un portale. In questo modo le pagine di un portale basato su portlet si presentano un po’ come un desktop con varie finestre aperte e disposte in maniera ordinata, alcune delle quali comunicano fra loro, mentre altre sono totalmente indipendenti, e tutte trascinabili nell’ambito di un layout tabellare.
Una portlet al suo interno può essere sviluppata usando diverse tecnologie come JSF, Struts, semplici JSP e servlet, JavaScript, CSS etc… Questa è proprio la chiave di volta per effettuare l’operazione di porting che mostreremo in questo e nel successivo articolo.
È noto che la mente umana non può fare a meno di effettuare collegamenti semantici fra le nozioni che apprende (con alcune rare eccezioni) e io, come “homo informaticus”, con questo articolo cercherò proprio di coniugare ciò su cui lavoro attualmente con ciò che più mi interessa personalmente: leggasi Porltet Liferay vs. GWT Web App.
Nello specifico vedremo come sia possibile creare una portlet nuova in Eclipse, utilizzando l’apposito plugin e riempirla con una applicazione GWT esistente completa di remote service e RPC. L’applicazione GWT sarà sviluppata anch’essa in Eclipse usando l’apposito Plugin Google per Eclipse.
Una cosa interessante, che emergerà, sarà il fatto che dal punto di vista del processo di sviluppo questa operazione potrà essere effettuata una tantum. Una volta risolto il porting come spiegato, si potrà continuare a sviluppare e debuggure la GWT Web App (utilizzando gli strumenti messi a disposizione dal Google Eclipse Plugin) direttamente dallo stesso progetto Eclipse della Portlet.
Ingredienti base
Di seguito riporteremo semplicemente una sorta di lista strumenti utilizzati per scrivere questo articolo, che possono servire a chi decide di seguire il percorso passo-passo in maniera pratica. come fosse un tutorial (il taglio della miniserie si presta in effetti a questo tipo di lettura).
L’IDE usato è Eclipse Galileo nella distribuzione J2EE con installati i seguenti plugin in aggiunta:
- Google Eclipse Plugin [GECLIPSE]
- GWT 2.03 già contenuto nel plugin di Eclipse [GWT]
- Portal Pack 2.0.1 [PPAC]
Come piattaforma di portale si è utilizzato Liferay 5.2.3 in versione bundled con Tomcat 6 [LIFT] (ci riferiremo alla sua home di installazione con il nome $TOMCAT_HOME).
A partire da questi strumenti andremo per prima cosa a creare un progetto di prova GWT che utilizzi le API di Liferay. Successivamente creeremo una portlet vuota su cui effettueremo il porting effettivo della Web App GWT. Infine creeremo il war e lo deployeremo come portlet su Liferay per il testing.
Creazione della Web App GWT
Per integrare a pieno i due mondi (GWT e Liferay) creeremo una applicazione GWT completa di parte client e parte server che utilizzi le API di Liferay per interrogare l’anagrafica utenti del portale e riportarne alcune informazioni base in un Dialog Panel di GWT.
Creazione del progetto
Partiamo quindi col creare un nuovo Web Application Project dal plugin Eclipse di Google (File -> New -> Web Application Project)
Aggiungiamo le librerie delle API che Liferay fornisce per usare i suoi servizi come LocalServices: portal-kernel.jar e portal-service.jar.
NOTA: nella distribuzione di Liferay bundled con Tomcat le librerie si trovano in $TOMCAT_HOME/lib/ext.
Per rendere il progetto più portabile, definiamo una Build Path Variable in Eclipse che punti a questa cartella e la chiameremo LIFERAY_LIBRARY.
Figura 3 – Build path del progetto e uso della LIFERAY_LIBRARY variable.
A questo punto dobbiamo ripulire i package generati dal plugin dalle classe di esempio create automaticamente dal wizard lasciando però la classe ArticoloMoka.java ma cancellando il contenuto del metodo OnModuleLoad() al suo interno per sostituirlo con il nostro in seguito.
Creazione del DTO
A questo punto creiamo il DTO (Data Transfer Object) che verrà usato per trasferire le informazioni dal server verso il client. Per far questo GWT prevede che si definiscano dei Java Bean ossia classi che implementino java.io.Serializable e presentino getter e setter relativi ai campi da rendere accessibili.
In questo caso il DTO si chiamerà LiferayUserDTO.java e definirà i seguenti attributi pubblicati tramite setter e getter:
- user_id: id numerico e univoco dell’utente nel DB di Liferay
- screern_name: nome utente usato da Liferay come identificativo dell’utente
- email_address: indirizzo e-mail univoco dell’utente
- public_page: URL relativo al portale della pagina pubblica dell’utente
- image_url: URL dell’immagine (se presente) dell’utente
e verrà collocato sotto il package apposito org.imola.gwt.articoloMoka.client.dto.
Creazione del LiferayGWTService: interfacce e implementazione
Creiamo ora il Remote Service GWT che userà le API di Liferay per reperire l’utente richiesto dal client. Per prima cosa, creiamo una interfaccia di nome LiferayGWTService.java che estende RemoteService sotto al package org.imola.gwt.articoloMoka.client:
@RemoteServiceRelativePath("liferay-gwt-service") public interface LiferayGWTService extends RemoteService { // Restituisce un LiferayUserDTO con le info relative all'utente screeName public LiferayUserDTO getUser_ByScreenName(String screenName); }
Automaticamente il plugin ci suggerirà (segnalando un errore nel codice) che è necessario creare la relativa interfaccia asincrona. Cliccando sull’errore possiamo selezionare la voce del suggerimento “Create Async Interface” e nello stesso package verrà creata la classe LiferayGWTServiceAsync.java come di seguito:
public interface LiferayGWTServiceAsync { void getUser_ByScreenName(String screenName, AsyncCallback callback); }
Sotto il package org.imola.gwt.articoloMoka.server creiamo la classe che implementa LiferayGWTService ed estende RemoteServiceServlet. Di seguito riportiamo un estratto del codice che implementa il metodo getUser_ByScreenName():
public class LiferayGWTServiceImpl extends RemoteServiceServlet implements LiferayGWTService { ... @Override public LiferayUserDTO getUser_ByScreenName(String screenName) { HttpServletRequest request = this.getThreadLocalRequest(); Map uinfo = (Map) request.getAttribute(PortletRequest.USER_INFO); int companyId = 1; if (uinfo!=null && uinfo.get("liferay.company.id")!=null) { companyId = Integer.valueOf(uinfo.get( "liferay.company.id").toString()); } else { logger.error("Errore nel reperire il valore di liferay.company.id dalla request!!!"); } User user = null; try { user = UserLocalServiceUtil.getUserByScreenName( companyId, screenName); } catch (PortalException e) { logger.error("Errore nella getUserByScreenName(" +companyId+", "+ screenName+")"); e.printStackTrace(); } catch (SystemException e) { // TODO Auto-generated catch block e.printStackTrace(); } return createLiferayUserDTOFromUser(user); } ... }
NOTA: createLiferayUserDTOFromUser() è un metodo private del service che semplicemente crea un nuovo LiferayUserDTO a partire dalle info contenute in uno User di Liferay.
Infine sarà necessario aggiornare il file /war/WEB-INF/web.xml con la definizione del servlet relativo al RemoteService:
... liferayServlet org.imola.gwt.articoloMoka.server.LiferayGWTServiceImpl liferayServlet /articolomoka/liferay-gwt-service ...
A questo punto la struttura del progetto dovrebbe risultare come mostrato in figura 4.:
La classe del modulo ArticoloMoka.java infine definirà una semplice interfaccia per inserire lo screenName dell’utente ricercato e un DialogBox che presenterà una semplice scheda sull’utente trovato contenente foto e link alla pagina pubblica su Liferay. Di seguito riportiamo un estratto del codice della classe ArticoloMoka.java che implementa Entry Point:
public class ArticoloMoka implements EntryPoint { private final LiferayGWTServiceAsync liferayGWTService = GWT.create(LiferayGWTService.class); public void onModuleLoad() { final Label label = new Label("Applicazione di esempio per porting ... "); final Label searchLabel = new Label("Ricerca utente Liferay per Screen Name"); final TextBox searchBox = new TextBox(); final Button submitButton = new Button("Cerca..."); final VerticalPanel vpanel = new VerticalPanel(); final HorizontalPanel opanel = new HorizontalPanel(); ... // ==== DialogBox === final DialogBox dialogBox = new DialogBox(); final Anchor userLink = new Anchor(); final Label resultLabel = new Label("Nessun utente trovato con screen name "); final Button closeButton = new Button("Close"); final Image userImage = new Image(); final VerticalPanel dialogBoxVpanel = new VerticalPanel(); final HorizontalPanel dialogBoxHpanel = new HorizontalPanel(); //...settaggio dei vari widget dialogBoxVpanel.add(resultLabel); dialogBoxHpanel.add(userImage); dialogBoxHpanel.add(userLink); dialogBoxVpanel.add(dialogBoxHpanel); dialogBoxVpanel.add(closeButton); closeButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { dialogBox.hide(); } }); dialogBox.setWidget(dialogBoxVpanel); // ==== Fine DialogBox === // ==== MainPanel === submitButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { liferayGWTService.getUser_ByScreenName(searchBox.getText(), new AsyncCallback() { @Override public void onFailure(Throwable caught) { resultLabel.setText( "Si e' verificato un errore sul server: " +caught.getMessage()); dialogBox.show(); } @Override public void onSuccess(LiferayUserDTO result) { if (result!=null) { GWT.log( "Trovato l'utente liferay con screen name " + searchBox.getText()); GWT.log( " - Url dell'immagine utente: " + result.getImage_url()); GWT.log( " - Url della pagina pubblica: " + result.getPubblic_page()); resultLabel.setText( "Trovato un utente liferay con screen name " +searchBox.getText()+": "); userImage.setUrlAndVisibleRect( result.getImage_url(), 0, 0, 100, 100); userImage.setVisible(true); userLink.setHref( result.getPubblic_page()); userLink.setText( "Pagina pubblica di " +result.getScreen_name()); userLink.setTitle( result.getPubblic_page()); } else { resultLabel.setText( "Nessun utente trovato con screen name " +searchBox.getText()); userLink.setVisible(false); } dialogBox.show(); } }); } }); vpanel.add(label); opanel.add(searchLabel); opanel.add(searchBox); vpanel.add(opanel); vpanel.add(submitButton); // === Fine MainPanel === RootPanel.get("mainPanel").add(vpanel); } }
Non ci dilungheremo oltre sull’entry point in quanto esula dallo scopo degli articoli che invece sono incentrati sulla trasformazione di questa applicazione in una portlet.
A questo punto potremo compilare il progetto tramite il comando “GWT Compile Project” che di fatto crea nella cartella “war” una sottocartella “articolomoka” con tutti i JavaScript generati dalla parte client. Zippando poi la cartella war e cambiando l’estensione da “ArticoloMoka.zip” ad “ArticoloMoka.war” avremo il file pronto per essere deployato sotto Tomcat come una normalissima WebApp. Il risultato è mostrato in figura 5.
Ora non resta che trasformare questa simpatica applicazione GWT in una portlet da incastonare nel portale Liferay… senza barare però! “Barare” sarebbe possibile e anche molto facile in questo caso. Basterebbe infatti usare una portlet di IFrame e configurarla per puntare alla pagina di questa web app… ma questo ovviamente non vorrebbe dire integrare GWT in una portlet.
Conclusioni
Per questo mese ci fermiamo qui, senza barare… Abbiamo introdotto la questione e abbiamo creato una webapp GWT, indicando i vari passi da seguire. Resta da fare la seconda parte, ossia il vero e proprio porting della webapp dentro una portlet Liferay. Nel prossimo numero, partendo da una dalla creazione di una portlet vuota, vedremo come farlo e concluderemo questa trattazione con alcune riflessioni.
Riferimenti
[LIFERAY] Sito ufficiale di Liferay
http://www.liferay.com/
[LIFT] Liferay Bundled con Tomacat 6
http://sourceforge.net/projects/lportal/files/Liferay%20Portal/liferay-portal-tomcat-6.0-5.2.3.zip
[GWT] Google Web Toolkit
http://code.google.com/intl/it-IT/webtoolkit/
[PPAC] Portal Pack per Eclipse
https://eclipse-portalpack.dev.java.net/
[MOKA1] – Giovanni Puliti – Il programmatore e le sue API – XX parte: realizzare la GUI web con GWT
https://www.mokabyte.it/cms/article.run?articleId=J3E-HC5-3JC-XGL_7f000001_18359738_834bfb23
[GECLIPSE] Google Eclipse Plugin
http://code.google.com/intl/it-IT/eclipse/
[SMGWT] Smart GWT
http://code.google.com/p/smartgwt/
[SDK] Liferay SDK Plugin
http://www.liferay.com/community/wiki/-/wiki/Main/Plugins%20SDK#section-Plugins+SDK-CreatingANewPortlet
Nato a Imola nel 1978, ha conseguito la laurea Specialistica in informatica nel 2005 presso l‘Università di Bologna con una tesi sull‘"Estrazione di Ontologie da Framework standardizzati EDI/XML". Per tre anni ha lavorato come ricercatore presso il centro ENEA di Bologna seguendo progetti sull‘applicazione di tecnologie semantiche a framework e standard per l‘interoperabilità come ebXML o UBL, pubblicando insieme ad altri ricercatori diversi articoli su tali argomenti.
Attualmente è consulente presso Imola Informatica S.r.l. dove si occupa di piattaforme Java EE based e progetti sul Semantic Web, e i suoi interessi principali si orientano alle piattaforme enterprise basate su middleware semantici, Ontology Engeenering e alle interfacce basate su AJAX e GWT.