Facendo tantissimi corsi su Liferay in giro per l'Italia ho l'opportunità di confrontarmi con differenti gruppi di sviluppo sui casi d'uso specifici dei loro progetti.
La risposta che sempre più spesso do alle domande del tipo"come facciamo a fare questa cosa con Liferay è "fatelo con un asset"! ;)
Per chi conosce già la piattaforma, il concetto di Asset non sarà di certo nuovo. Anzi:è una delle funzionalità che rende il portale "potente e versatile" come noi lo vediamo da fuori!
Nel dettaglio, il portale gestisce due tipi di oggetti: "Resource" e "Asset".
Una "Resource" non è altro che un oggetto inserito all'interno del portale sul quale può insistere il permission system. Ogni entry che fate con i metodi del service builder dovrebbe essere inserita anche nel ResourceFramework, proprio per permettere al sistema di gestione dei permessi di gestirne l'accesso.
Un "Asset", invece, è l'elevamento a "oggetto gestibile dagli utenti"di una risorsa. In poche parole, un Asset non è altro che uno speciale tipo di risorsa che però è compresa dal portale come "oggetto di dominio"che può essere utilizzato, ad esempio, direttamente all'interno dell'AssetPublisher oppure sul quale si possono creare custom fields e collegare tag e categories.
In questo post realizzaremo una nostra entità custom che il portale utilizzerà come Asset.
Come al solito, poniamoci un obiettivo da raggiungere e vediamo come, attraverso il portale, riusciremo a realizzare la funzionalità.
Nel nostro esempio, poniamo di voler creare una entità "Progetto" che possa essere "estratta" dagli utenti solamente configurando l'AssetPublisher.
Per raggiungere questo obiettivo, dovremo agire su quattro fronti:
Per prima cosa dobbiamo creare la nostra entità. Lo scopo di questo post non è quello di spiegare il funzionamento del service builder; quindi andremo velocemente su questo punto.
Vi ricordo però l'articolo che abbiamo scritto su MokaByte relativo a questo tema, al quale potete fare riferimento se volete approfondire il dettaglio della tematica qui esposta.
Molto rapidamente:
- create attravero la Liferay IDE un nuovo "Liferay Service Builder"
- mappate la vostra entità custom
- lanciate il task ant "
build-service
" - fate refresh del progetto! ;)
Se tutto va come deve, otterrete tutti gli oggetto di modello, persistenza e servizi per gestire l'entità appena mappata.
A questo punto dobbiamo "insegnare" a Liferay a gestire la nostra entità in relazione al permission system.
Questo passo è necessario perchè il portale richiede, per funzionare con il ResourceFramework e l'AssetFramework che siano presenti delle permission sull'entità che vogliamo gestire.
Per raggiungere questo obiettivo dobbiamo creare un file, all'interno della nostra src di progetto che chiameremo portlet.properties
.
Fate attenzione! È portlet non portal il nome del file! È molto comune fare questo errore; ve lo segnalo perchè anche io ogni tanto, facendo le cose "di corsa", ci casco! ;)
All'interno di questo file inserite questa riga:
resource.actions.configs=resource-actions/default.xml
Questa istruzione, al momento del deploy, dirà a Liferay dove si trova il nostro file relativo alle action sulle nostre risorse. Gli dice, in sostanza, quali permessi deve permettere di applicare su una risorsa per consentire agli utenti, attraverso l'interfaccia di portale, di configurare i ruoli e le relative permission sia sulla nostra portlet che sulle risorse da essa gestite.
Approfondirò in un post successivo questa tematica, perchè il permission system di Liferay è davvero molto ben fatto e dominarlo correttamente ci può essere di grande aiuto per realizzare soluzioni integrate nel portale!:)
Una volta creato il file ed inserita la riga, dobbiamo creare, sempre dentro alla nostra src
di progetto, il file default.xml
nel folder resource-actions
.
Qui un esempio di questo file:
<?xml version="1.0" encoding="UTF-8"?>
<resource-action-mapping>
<portlet-resource>
<!-- Qui mappiamo il portlet-name del nostro portlet
letto dal file portlet.xml -->
<portlet-name>gestioneprogetto</portlet-name>
<permissions>
<supports>
<!-- come vedete possiamo usare anche chiavi
custom, figata!:) -->
<action-key>ADD_PROGETTO</action-key>
<action-key>UPDATE_PROGETTO</action-key>
<action-key>DELETE_PROGETTO</action-key>
<action-key>VIEW</action-key>
</supports>
<community-defaults>
<action-key>VIEW</action-key>
</community-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>ADD_PROGETTO</action-key>
<action-key>UPDATE_PROGETTO</action-key>
<action-key>DELETE_PROGETTO</action-key>
</guest-unsupported>
</permissions>
</portlet-resource>
<model-resource>
<!-- qui mappiamo il model che abbiamo generato e sul
quale potremo agire per dare le permission -->
<model-name>
it.dvel.test.service.builder.model.Progetto
</model-name>
<portlet-ref>
<portlet-name>gestioneprogetto</portlet-name>
</portlet-ref>
<permissions>
<supports>
<!-- anche qui, volendo, potremmo impostare
azioni custom per il nostro modello;
altra figata!:) -->
<action-key>DELETE</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>UPDATE</action-key>
<action-key>VIEW</action-key>
</supports>
<community-defaults>
<action-key>VIEW</action-key>
</community-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>UPDATE</action-key>
<action-key>DELETE</action-key>
</guest-unsupported>
</permissions>
</model-resource>
</resource-action-mapping>
Credo che il file sopra riportato sia auto esplicativo; nel caso ci fossero domande potete farle commentando questo post! :)
Sarò ben felice di rispondervi!! :D
Una volta salvato questo file, al momento del deploy, Liferay caricherà questo file e mapperà nel permission system queste risorse e le relative azioni configurabili; così che dall'interfaccia del pannello di controllo l'utente possa profilarle secondo le proprie esigenze.
Andiamo avanti! ;)
È qui che accade la magia: ogni volta che inseriamo una entity nel database attraverso gli oggetti del service builder, dobbiamo dire ai due framework sopra citati come gestirle e come mapparle.
La domanda che vi sorgerà spontanea sarà sicuramente "ma perchè non lo fa in automatico il service builder"?:)
Bhe, perchè come sempre accade in Liferay, i progettisti della piattaforma vi lasciano la libertà di fare quello che volete. Ovviamente dandovi la possibilità di scegliere ma mettendovi a supporto tutto il necessario per fare le cose "in fretta e bene"! :)
Nel dettaglio, comunque, prima di procedere con il codice, dobbiamo iniettare nel nostro service gli oggetti che utilizzano l'AssetFramework perchè, al contrario di quelli che gestiscono il ResourceFramework, questi non sono presenti di default nei nostri servizi custom.
Per fare questa iniezione dobbiamo nuovamente agire sul file service.xml
.
Andate all'interno della nostra entity "Progetto"e, in fondo a tutto, mettete questa riga:
<reference package-path="com.liferay.portlet.asset" entity="AssetEntry" />
Una volta fatto questo:
- rigenerate con il task di ant '
build-service
' i servizi - fate refresh del progetto! ;)
Una volta compiuta questa operazione, troverete all'interno dell'oggetto "ProgettoServiceBaseImpl
" l'iniezione degli oggetti dell'AssetEntry:
@BeanReference(type = AssetEntryLocalService.class)
protected AssetEntryLocalService assetEntryLocalService;
@BeanReference(type = AssetEntryService.class)
protected AssetEntryService assetEntryService;
@BeanReference(type = AssetEntryPersistence.class)
protected AssetEntryPersistence assetEntryPersistence;
Ora possiamo inserire le nostre entity custom anche all'interno dell'AssetFramework! ;)
Vediamo come!
Per prima cosa dobbiamo far si che ogni volta che si inserisce / modifica / cancella una entity nel database i due framework sopra citati siano notificati di questa cosa.
Per farlo quindi occorrerà modificare i nostri metodi di persistenza perchè si prendano carico di fare queste operazioni.
Nel dettaglio, quindi, andremo ad agire sulla nostra classe di implementazione, in modo che sia "trasparente" per i nostri client che l'entity sia censita anche in questi framework.
Nel dettaglio, quindi, editate la classe "ProgettoLocalServiceImpl
" che trovate tra gli oggetti generati dal service builder.
Qui dovremo aggiungere qualche metodo che sarà poi utilizzato dai nostri client per gestire le entity custom verso il database.
Una nota:noi modificheremo solo la parte "Local"; la parte, cioè, che va verso il database.
Se volete portare questo comportamento anche alle entity gestite con i web service SOAP o REST, potete farlo incapsulando questa logica anche nell'oggetto "ProgettoServiceImpl
".
Nella nostra classe di implementazione, aggiungiamo questi medoti:
public Progetto addProgetto(Progetto progetto,
ServiceContext ctx) throws PortalException, SystemException {
super.addProgetto(progetto);
resourceLocalService.addResources(
progetto.getCompanyId(), progetto.getGroupId(),
progetto.getUserId(),
Progetto.class.getName(), progetto.getPrimaryKey(),
false,
true, true);
assetEntryLocalService.updateEntry(
progetto.getUserId(), progetto.getGroupId(),
Progetto.class.getName(),
progetto.getPrimaryKey(),
ctx.getAssetCategoryIds(),
ctx.getAssetTagNames());
return progetto;
}
@Override
public void deleteProgetto(long progettoId)
throws PortalException,
SystemException {
Progetto progetto = getProgetto(progettoId);
deleteProgetto(progetto);
}
@Override
public void deleteProgetto(Progetto progetto)
throws SystemException, PortalException {
resourceLocalService.deleteResource(
progetto.getCompanyId(), Progetto.class.getName(),
ResourceConstants.SCOPE_INDIVIDUAL,
progetto.getPrimaryKey());
assetEntryLocalService.deleteEntry(
Progetto.class.getName(), progetto.getPrimaryKey());
super.deleteProgetto(progetto);
}
Cosa facciano questi tre metodi è evidente:ogni volta che una entity viene aggiunta o rimossa notificano questa operazione anche ai nostri due framework.
Bhe, che ci crediate o no, il più è fatto!
Ora dobbiamo dare una "visualizzazione" del nostro Asset che Liferay utilizzerà ogni volta che un estrattore dinamico o all'interno di una componente d'interfaccia standard -come, ad esempio, il motore di ricerca che analizzerò in un post successivo!:)-
E siamo così arrivati in fondo al nostro lavoro! Dobbiamo ora creare gli oggetti che istruiranno il portale su come visualizzare il nostro Asset!
Le classi che ci interessa creare sono delle estensioni di queste due classi di Liferay:
- La factory che produce le view degli Asset:
BaseAssetRendererFactory
- L'implementazione della nostra view:
BaseAssetRenderer
La prima cosa da fare è però, come sempre, dire a Liferay come trattare le nostre classi al momento del deploy; per farlo, quindi, andiamo ad agire sul nostro liferay-portlet.xml
ed inseriamo questa riga (dopo il tag "icon
", così fate prima che a guardare la DTD.. :)).
<asset-renderer-factory>
it.dvel.test.renderer.ProgettoRenderFactory
</asset-renderer-factory>
Questa riga dirà infatti a Liferay qual'è la nostra implementazione da utilizzare quando deve gestire Asset sull'interfaccia.
Vediamo ora l'implementazione di questa classe; premetto che è una cosa molto semplice da fare:la classe base, infatti, nasconde ed implementa già le funzionalità "core" del portale; a noi non resta che dare una nostra implementazione, in modo che sia "veloce" creare questo tipo di struttura.
Nel dettaglio, ecco la classe <omissis voluto degli import.. :)>:
public class ProgettoRenderFactory extends BaseAssetRendererFactory {
@Override
public AssetRenderer getAssetRenderer(long classPK, int type)
throws PortalException, SystemException {
Progetto progetto =
ProgettoLocalServiceUtil.getProgetto(classPK);
return new ProgettoAssetRenderer(progetto);
}
@Override
public String getClassName() {
return Progetto.class.getName();
}
@Override
public String getType() {
// Questa, francamente, è una cosa non
// corretta, però così funziona.. :)
return "progetto";
}
}
Fatto! :)
Questa factory è in grado di fornire al portale la nostra implementazione custom per la visualizzazione degli Asset!
Manca però ancora una classe; quella che fisicamente decide come visualizzare il nostro codice.
Vediamo quindi la seconda classe: ProgettoAssetRenderer
.
Anche qui, come prima, omissis voluto degli import..
public class ProgettoAssetRenderer extends BaseAssetRenderer {
Progetto progetto = null;
// L'istanza di model da visualizzare ci viene passata dalla
// factory; così noi ce la salviamo internamente
// per riusarla "alla bisogna"!:)
public ProgettoAssetRenderer(Progetto progetto) {
this.progetto = progetto;
}
@Override
public long getClassPK() {
return progetto.getPrimaryKey();
}
@Override
public long getGroupId() {
return progetto.getGroupId();
}
@Override
public String getSummary(Locale locale) {
return progetto.getDescrizione();
// Oquello che volete che sia la descrizione..
}
@Override
public String getTitle(Locale locale) {
return progetto.getNome();
// Oquello che volete che sia il titolo..
}
@Override
public long getUserId() {
return progetto.getUserId();
}
@Override
public String getUuid() {
return null;
// Nel mio progetto non ho generato gli UUID per la
// mia entity, così qui non ritorno nulla. Se lo avessi
// fatto, però, qui avrei usato il metodo che mi avrebbe
// generato il service builder sulla entity.
}
@Override
public String render(RenderRequest renderRequest,
RenderResponse renderResponse, String template)
throws Exception {
// passo in pagina la entity
renderRequest.setAttribute("progetto", this.progetto);
// decido, nel caso sia in visualizzazione "completa"
// dell'Asset, quale JSP usare.
if (TEMPLATE_FULL_CONTENT.equalsIgnoreCase(template)) {
return "/html/gestioneprogetto/progetto_full_template.jsp";
}
// Se ritorno null per il TEMPLATE_ABSTRACT ci penserà
// il portale a generare la view usando title e description
// ovviamente potrei passare anche qui una JSPe fornire
// una mia view custom dell'Asset nel formato lista
// dell'AssetPublisher; semplicemente ritornando la JSP opportuna..
return null;
}
}
That's it!
Compilate, fate deploy e "provare per credere"! ;)
Le nostre entity saranno presenti all'interno dell'AssetPublisher semplicemente selezionando il nostro model come "discriminante di visualizzazione"; nel sistema di gestione dei ruoli potremo impostare le nostre permission sui modelli e sul portlet e anche dalle preference di portlet vedremo esposte le nostre azioni!
Direi che abbiamo raggiunto l'obiettivo! :)
Ora siete pronti a giocare con il portale come meglio credete! ;D
Fatemi sapere se funziona tutto a dovere; ho scritto questo post di domenica mattina prima di portare mia figlia al parco!:) Tutto il codice che c'è qui riportato, però, è preso da un progetto che funziona e che fa quello che ci eravamo proposti di fare!
A presto!:)