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: