bloggers bloggers

Marco Napolitano
Messaggi: 79
Stelle: 0
Data: 17/02/22
Jader Jed Francia
Messaggi: 63
Stelle: 0
Data: 18/02/21
Paolo Gambetti
Messaggi: 2
Stelle: 0
Data: 11/11/19
Katia Pazzi
Messaggi: 1
Stelle: 0
Data: 27/06/19
Ezio Lombardi
Messaggi: 11
Stelle: 0
Data: 10/04/18
Chiara Mambretti
Messaggi: 25
Stelle: 0
Data: 27/02/17
Serena Traversi
Messaggi: 3
Stelle: 0
Data: 21/07/16
Francesco Falanga
Messaggi: 8
Stelle: 0
Data: 14/06/16
Antonio Musarra
Messaggi: 2
Stelle: 0
Data: 18/11/13
Simone Celli Marchi
Messaggi: 6
Stelle: 0
Data: 09/07/13
Indietro

Esporre un web service con Liferay e consumarlo da remoto

Grazie al service builder, in liferay è possibile creare rapidamente lo strato di accesso ai dati di una qualsiasi nostra tabella custom.

Con l'aiuto della Liferay IDE per Eclipse, questo processo è reso ancora più semplificato: è possibile, infatti, generare direttamente dalla IDE i file di service builder e, da qui, generarne i sorgenti.

Supponiamo di voler creare una nostra entità custom e di volere che questa entità sia poi consumata dall'esterno, usando il protocollo SOAP.

Per farlo, creeremo prima di tutto il nostro file service.xml, che sarà fatto più o meno così:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.0.0//EN"

"http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd">
<service-builder package-path="it.dvel.test">
<author>jed</author>
<namespace>foo</namespace>

<entity name="Foo" local-service="true" remote-service="true">

<!-- PK fields -->

<column name="fooId" type="long" primary="true" />

<!-- Audit fields -->

<column name="companyId" type="long" />
<column name="userId" type="long" />
<column name="userName" type="String" />
<column name="createDate" type="Date" />
<column name="modifiedDate" type="Date" />

<!-- Other fields -->

<column name="field1" type="String" />
<column name="field2" type="boolean" />
<column name="field3" type="int" />
<column name="field4" type="Date" />
<column name="field5" type="String" />

<!-- Order -->

<order by="asc">
<order-column name="field1" />
</order>

<!-- Finder methods -->

<finder name="Field2" return-type="Collection">
<finder-column name="field2" />
</finder>
</entity>
</service-builder>

L'attributo evidenziato in nero remote-service="true" istruisce il service builder a generare anche i file di interfaccia per l'invocazione remota.

Nello specifico, questo file creerà, all'interno del package it.dvel.test.service il file FooServiceImpl.java, che è quello che dovremo arricchire con i metodi che vogliamo esportare come web service. Liferay, infatti, anche se noi specifichiamo l'attributo per la generazione dei remote service, di default non esporta nessun metodo di quelli generati per la gestione della singola entità, ma lascia al programmatore l'onere di farlo.

All'interno di questo file, quindi, noi dovremo andare a mettere i metodi che vogliamo rendere disponibili come web service.

Modifichiamo quindi il file come segue:


import it.dvel.test.model.*;
import it.dvel.test.model.impl.*;
import it.dvel.test.service.*;
import it.dvel.test.service.base.FooServiceBaseImpl;

/**
* The implementation of the foo remote service.
*
* <p>
* All custom service methods should be put in this class.

* Whenever methods are added, rerun ServiceBuilder to copy their

* definitions into the {@link it.dvel.test.service.FooService}

* interface.
* </p>
*
* <p>
* Never reference this interface directly.

* Always use {@link it.dvel.test.service.FooServiceUtil} to access

* the foo remote service.
* </p>
*
* <p>
* This is a remote service. Methods of this service are expected

* to have security checks based on the propagated JAAS credentials

* because this service can be accessed remotely.
* </p>
*
* @author jed
* @see it.dvel.test.service.base.FooServiceBaseImpl
* @see it.dvel.test.service.FooServiceUtil
*/

public class FooServiceImpl extends FooServiceBaseImpl {

public String sayHelloToName(String name) {
return "Hello " + name;
}

/* Una piccola nota:qui noi vogliamo utilizzare da remoto

* l'entità generata per farci qualche cosa. Non possiamo usare

* direttamente la classe it.dvel.test.model.Foo, ma dobbiamo usare

*il wrapper per il protocollo SOAPche Liferay ci mette a

* disposizione con la generazione

* degli oggetti remoti:it.dvel.test.model.FooSoap.

* Nelle note del post spiegherò meglio il motivo! :)

*/
public FooSoap addSomethingTo(FooSoap aFoo) {
Foo myFoo = FooModelImpl.toModel(aFoo);
myFoo.setField1(myFoo.getField1() + " Added by service converted in Foo object");
return FooSoap.toSoapModel(myFoo);
}
}

Una volta modificata la classe, per far si che i nostri metodi siano visibili dall'interfaccia del servizio, rigeneriamo con il service builder gli oggetti dell'entità. Per farlo, dobbiamo semplicemente rilanciare la generazione del codice.

Questo farà si che i nostri metodi vengano caricati all'interno degli oggetti generati dal service builder e che quindi siano dichiarati nell'interfaccia del servizio:it.dvel.test.service.FooService.

Una volta rigenerati gli oggetti, siamo pronti per generare il servizio SOAP.

Per farlo, ci basterà lanciare la generazione del WSDD, anche questa disponibile direttamente dalla Liferay IDE.

Una volta generato tutto, facciamo deploy del nostro servizio.

Dopo il deploy, Liferay 6 renderà disponibili i nostri servizi direttamente all'url del nostro hook e non all'interno della lista dei servizi di Liferay, quindi potrete vedere i servizi esposti a questo URL: http://127.0.0.1:8080/MyFirstHook-hook/axis

Invocando questa URL, quindi, avremo accesso alla lista dei servizi che il nostro hook espone e potremo generare, in un nuovo progetto, il client web service per utilizzarli. Per farlo io ho usato il toolkit axis, dandogli in pasto la URL sopra riportata.

Una volta generato il client nel nuovo progetto, ho creato questo codice di esempio per l'invocazione:

import it.dvel.test.model.*;
import it.dvel.test.service.http.*;

public class Invocator {

public static void main(String[] args) throws Exception {
FooServiceSoapServiceLocator locator =

new FooServiceSoapServiceLocator();
FooServiceSoap service = locator.getPlugin_foo_FooService();
String hello = service.sayHelloToName("Jader");
System.out.println("hello -->" + hello + "<--");
FooSoap soap = new FooSoap();
soap.setField1("Settato dal client");
FooSoap anotherSoap = service.addSomethingTo(soap);
System.out.println("anotherSoap.getField1() -->" +

anotherSoap.getField1() + "<--");
}

}

La sua esecuzione genererà questo output:

hello -->Jader<--

anotherSoap.getField1() -->Settato dal client Added by service converted in Foo<--

Una nota relativa al motivo per cui dobbiamo usare l'oggetto FooSoap e non direttamente l'interfaccia Foo all'interno della nostra classe it.dvel.test.service.FooServiceImpl.

Il motivo è molto semplice: estendendo l'interfaccia it.dvel.test.model.FooImpl, l'interfaccia it.dvel.test.model.Foo dichiara una serie di metodi che, serializzati via SOAP, non porterebbero nessun valore aggiunto, anzi! Renderebbero la serializzazione molto più costosa e "sporcherebbe" la definizione degli oggetti Soap con una serie di oggetti e di metodi inutili.

Si veda, nello specifico, l'interfaccia FooImpl, nella definizione dei metodi a consumo esclusivo del framework Liferay:

public Foo toEscapedModel();
public boolean isNew();
public void setNew(boolean n);
public boolean isCachedModel();
public void setCachedModel(boolean cachedModel);
public boolean isEscapedModel();
public void setEscapedModel(boolean escapedModel);
public Serializable getPrimaryKeyObj();
public ExpandoBridge getExpandoBridge();
public void setExpandoBridgeAttributes(ServiceContext serviceContext);
public Object clone();
public int compareTo(Foo foo);
public int hashCode();
public String toString();
public String toXmlString();

Per evitare di sporcare quindi il client con oggetti e logiche non serializzabili e non utili al client stesso, si è scelto di esporre un bean semplice (appunto:it.dvel.test.model.FooSoap) che, una volta generato anche dal client axis, non avrebbe dato nessun problema di marshall / unmarshall.

Sono poi stati forniti dei metodi di utility per creare da e verso questo modello gli oggetti che implementano l'interfaccia it.dvel.test.model.Foo.

Nei fatti:

classe: it.dvel.test.model.FooSoap

public static FooSoap toSoapModel(Foo model) {
FooSoap soapModel = new FooSoap();

soapModel.setFooId(model.getFooId());
soapModel.setCompanyId(model.getCompanyId());
soapModel.setUserId(model.getUserId());
soapModel.setUserName(model.getUserName());
soapModel.setCreateDate(model.getCreateDate());
soapModel.setModifiedDate(model.getModifiedDate());
soapModel.setField1(model.getField1());
soapModel.setField2(model.getField2());
soapModel.setField3(model.getField3());
soapModel.setField4(model.getField4());
soapModel.setField5(model.getField5());

return soapModel;
}

classe: it.dvel.test.model.impl.FooImpl

public static Foo toModel(FooSoap soapModel) {
Foo model = new FooImpl();

model.setFooId(soapModel.getFooId());
model.setCompanyId(soapModel.getCompanyId());
model.setUserId(soapModel.getUserId());
model.setUserName(soapModel.getUserName());
model.setCreateDate(soapModel.getCreateDate());
model.setModifiedDate(soapModel.getModifiedDate());
model.setField1(soapModel.getField1());
model.setField2(soapModel.getField2());
model.setField3(soapModel.getField3());
model.setField4(soapModel.getField4());
model.setField5(soapModel.getField5());

return model;
}

Enjoy! :)

Alcune referenze:

Liferay Service Builder:

Precedente
Commenti
Aggiungi Commento
Antonio Musarra
Ciao Jed,
mettiamo il caso che dovessi estendere l'interfaccia nativa esposta dalla Portlet degli annunci in particolare il servizio Portlet_Announcements_AnnouncementsEntryService in modo da aggiungere un altro metodo che accetti uno modello dati specifico (non di Liferay).

Tu come realizzeresti la cosa ?

Tutto questo perchè un sistema vuole imporre il suo formato di messaggi di scambio e non utilizzare semplicemente il client già bello e pronto in dotazione con Liferay.

Grazie,
Antonio.
Inviato il 09/07/15 14.02.
Jader Jed Francia
Ciao Antonio!
Prima di risponderti mi servirebbe sapere che cosa intendi quando dici "un modello di dati specifico". O meglio, mi servirebbe capire il protocollo con il quale questo sistema vuole passarti i dati.

Ti passerebbe uno stream XML su HTTP oppure ha proprio un suo formato proprietario?

Fammi sapere! ;)

Ciao, J.
Inviato il 09/07/15 14.02 in risposta a Antonio Musarra.
Antonio Musarra
Ciao Jed,
vedo solo adesso il messaggio, mail il blog non ha un servizio di notifica via mail ?

Il protocollo sarebbe SOAP over HTTP, quindi l'interfaccia dei Web Service già offerta da Liferay in particolare il servizio Portlet_Announcements_AnnouncementsEntryService.

Il discorso era semplicemente questo. Il metodo addEntry del servizio accetta una serie di parametri (ha una determinata firma), io tramite un Hook volevo aggiungere un metodo aggiuntivo all'interfaccia con una firma ad hoc per soddisfare determinate esigenze.

Comunque ho risolto con una soluzione molto più indicata. Lo scenario è il seguente. Uno o più sistemi che fanno parte di una piattaforma informativa nell'ambito energetico, di cui Liferay è il portale, necessitano d'inoltrare le proprie notifiche ad utenti/gruppi visualizzandolo sull'home del portale tramite la Portlet Announcements. Il messaggio di trasporto della notifica è standard per tutta la piattaforma e deve essere recepito dalla Portlet Announcements.

Dato che nel progetto sono responsabile della parte ESB, ho deciso di pubblicare un servizio di notifica che accetta il messaggio standard di notifica della piattaforma e successivamente, dopo le dovute trasformazioni lo inoltra a Liferay via WS.

Bye e Grazie,
Antonio.
Inviato il 09/07/15 14.02 in risposta a Jader Jed Francia.
Giuseppe Pantaleo
ciao,
sto utilizzando il service builder di Liferay 6.1; quando il task build-service genera la classe che implementa le utility SOAP, quello che ottengo come risultato sono una serie di metodi che generano errori quando provo ad eseguire il comando build-wsdd.
Ho provato ad indagare sul forum ufficiale di Liferay, ma non sono riuscito ad ottenere delle risposte soddisfacenti; quello che dovrei fare, sarebbe modificare a mano il codice generato, con il rischio di perdere le modifiche, nel caso dovessi rilanciare il comando build-service...
Che voi sappiate, ci sono bug conosciuti in merito a questo? Oppure c'è qualcosa di sbagliato in quello che faccio io?
Mi conviene utlizzare la versione 6.0 di Liferay? (non ci ho ancora provato, ma ci sto pensando)

Grazie,
Giuseppe
Inviato il 09/07/15 14.02.
Jader Jed Francia
Ciao Giuseppe!
No, non ci sono bug noti su questa parte.. Almeno: non ce ne sono di mia conoscenza! emoticon
Però utilizzo abitualmente il service builder e anche oggi ho generato un WSDD deployato senza difficoltà sull'ambiente di sviluppo -anche nel mio caso 6.1-.

Che tipo di errori hai?
Facci sapere, così vediamo come possiamo aiutarti! emoticon

Ciao, J.
Inviato il 09/07/15 14.02 in risposta a Giuseppe Pantaleo.
Giuseppe Pantaleo
Ciao Jed,
innanzitutto grazie per la tua risposta repentina.
Dopo aver "impiegato" un po' di ore, mi sono accorto che nell'implementazione di alcuni metodi dell'interfaccia remota del servizio ho utilizzato (sbadatamente!) come oggetto di trasporto it.dvel.test.model.Foo (facendo riferimento all'esempio del blog) e non it.dvel.test.model.FooSoap. E guarda caso erano proprio quegli oggetti a generare il problema, che riporto a futura memoria:
"java.io.IOException: Type {http://model.test.dvel.it}Foo is referenced but not defined"

Morale della favola: dovrei aver risolto! :-)

Ciao e grazie,
Giuseppe
Inviato il 09/07/15 14.02 in risposta a Jader Jed Francia.
Jader Jed Francia
Molto bene! emoticon
Se ti occorre altro, siamo qui! ;)

Ciao, J.
Inviato il 09/07/15 14.02 in risposta a Giuseppe Pantaleo.
Nicola Six
Ciao a tutti,
Ottimo articolo! dalla versione 6.1.0 il nuovo indirizzo per accedere al servizio è:

http://127.0.0.1:8080/MyFirstHook-hook/api/axis
Inviato il 09/07/15 14.02.