Buongiorno a tutti e ben ritrovati con "Le cronache di Jader", appunti -quasi- sensati per sopravvivere al difficile mondo dell'IT su Liferay!
Oggi vi parlerò di un problema che i miei animali mitologici mi hanno posto la scorsa settimana: mantenere sincronizzati i permessi tra due modelli differenti!
Come al solito, partirò raccontandovi il perché di questo requisito con un caso d'uso che spero sia sensato, così che possiate anche voi trarne spunto e capire velocemente se fa al caso vostro (non il caso d'uso, la soluzione! :)).
Mantenere sincronizzati i permessi di due modelli differenti: caso d'uso
Il caso d'uso è semplice: ho due oggetti (nel mio caso un oggetto custom che chiameremo, con enorme fantasia ;), ModelloCustom
che al suo interno ha una referenza ad un DlFileEntry
.
Il caso d'uso vuole che, a prescindere da quale delle due entità io parta a modificare i permessi, i permessi dell'altra entità siano mantenuti sincroni.
Quindi, per spiegarmi più semplicemente, se io modifico i permessi su ModelloCustom
aggiungendo, che ne so, il permesso di VIEW
e il permesso di UPDATE
a uno specifico ruolo, coerentemente con questo aggiornamento mi troverò modificati gli stessi due permessi sullo stesso ruolo dell'entità DlFileEntry
.
Cose da NON fare per gestire questo caso d'uso!
Prima di dirvi come fare a gestire questo caso :), vi dico quello che non dovete fare per gestirlo! :)
Intendo che si, anche io ci ho messo un po' per risolverlo e, nel mio "tentare", ho provato diverse strade per riuscirci.
Abusare dei value object listener: cattiva idea.. :/
La prima strada che ho percorso era quella dei value ojbect listener: ho creato dei listener (che dovrebbero servire a gestire l'integrità referenziale ma dei quali io ho abusato per i miei scopi.. ;)), uno per ModelloCustom
e uno per DlFileEntry
intercettando la modifica all'evento relativo sull'entità ResourcePermission
.
Sembrava funzionare ;), però poi mi sono accorto che gli update finivano per "rompere" i permessi; nel senso: le update venivano propagate correttamente ma, non so il perché :), quando il pannellino dei permessi si ricaricava questo mostrava permessi diversi da quelli che avevo impostato.
Sicuramente i miei animali mitologici si sarebbero lamentati di questo comportamento un po' bizzarro ;), quindi ho scartato questa implementazione e ho proseguito nel mio scouting..
Service Wrapper: panacea di tutti i miei mali?
Ovviamente la risposta è.. NO!
Posto che non ho capito perché ma la 7.1.2 non mi caricava il ServiceWrapper
che avevo creato (o meglio: caricare lo caricava, però non lo invocava e questa cosa mi aveva un po' tediato..) ho pensato che comunque non sarebbe stata una gran implementazione: il service della ResourcePermission
espone, #chevelodicoafare :), mille milioni di metodi e scoprire quali fossero quelli giusti mi sembrava già di per se troppo sbatti.. :)
Forte del fatto poi che non riuscivo a far invocare il mio ServiceWrapper
mi sono arreso in -credo- 8 minuti scartando anche questa strada!
E allora.. Custom sia! :)
Confrontandomi con l'animale mitologico che nel frattempo stava umoralmente cambiando il suo mood nei confronti della cosa, sentendolo sempre più disperato e triste (e con l'avvicinarsi del terribile momento del Collaudo col Cliente -fase della vita di un (quasi) dev che mette sempre una certa adrenalina, ammettiamolo! :D) mi sono detto "beh, sai che c'è? Che se il pannellino maledetto standard non fa quello che voglio io, allora me ne faccio uno custom dove piloto quello che mi pare sui permessi e amici come prima!".
L'idea, parlandone anche con il mio animale mitologico, sembrava addirittura sensata! Ci siamo lasciati alla fine della telefonata super convinti che questa fosse LA strada!
... E invece no!
Questa cosa del custom a tutti i costi non è mai una buona idea, soprattutto quando lavori con un mastodonte come Liferay!
E quindi? Che ho fatto?
Beh, premetto che.. A posteriori "la cosa del custom a tutti i costi non è mai una buona idea", ma sul momento.. Ero partito per implementarla! :)
E siccome sono uno pigro mi son detto "non è che devo reinventare la ruota: guardiamo come fa Liferay a gestire la costruzione del pannellino e come legge poi i parametri dalla matrice Azioni / Ruoli che disegna e replico tutto nel mio pannellino custom!".
Mamma mia quanto sono pigro e furbo.. :D
Per fortuna so ancora leggere il codice (anche se faccio queste cose tipo alle 4 della mattina, ma vabbé..) e, facendo scouting del codice della com.liferay.portlet.configuration.web.internal.portlet.PortletConfigurationPortlet
e più precisamente il suo simpa metodo updateRolePermissions
ho conosciuto dei nuovi amici.. :)
PermissionPropagator: ecco la risposta che cercavo! ;)
Spoiler ON: faccio presente che l'obiettivo funzionale per il quale i PermissionPropagator sono stati inventati NON è quello al quale ambisco io. In realtà questi cosi sono stati progettati per Propagare sulle entità figlie i permessi (ad esempio se cambio il permesso di visibilità su un thread del forum e vorrei che tutte le risposte al suo interno fossero automaticamente allineate senza doverle modificare a mano..).
Però è anche vero che, alla fin fine, il mio obiettivo è simile: anche io vorrei che si propagassero i permessi tra il mio ModelloCustom
e la mia DlFileEntry
!
Quindi ho proceduto, senza indugio, a testare questa soluzione!
Vediamo un po' di codice, per capire se questa roba ha senso anche per voi! :)
Annotiamo la classe come Component
@Component(
immediate = true,
property = {
"javax.portlet.name=" + PortletKeys.MODELLO_CUSTOM,
"javax.portlet.name=" + PortletKeys.DOCUMENT_LIBRARY_ADMIN
},
service = PermissionPropagator.class
)
Siccome io volevo bidirezionalmente aggiornare i permessi, quindi sia che si partisse dai permessi di ModelloCustom
sia che si partisse dai permessi del DlFileEntry
, ho mappato il mio PermissionPropagator
su entrambe le portlet di gestione!
Come sempre esiste una Base.. :)
public class MyPermissionPropagator extends BasePermissionPropagator {
...
}
Anche in questo caso ho esteso la classe base che Liferay mette sempre a disposizione...
Implementiamo il metodo richiesto dall'interfaccia!
@Override
public void propagateRolePermissions(ActionRequest actionRequest,
String className, String primKey, long[] roleIds) throws PortalException {
if (ModelloCustom.class.getName().equals(className)) {
propagateRolePermissionsToFileEntry(actionRequest, className, primKey, roleIds);
} else if (DLFileEntry.class.getName().equals(className)) {
propagateRolePermissionsToModelloCustom(actionRequest, className, primKey, roleIds);
}
}
Ho poi implementato il metodo che l'interfaccia PermissionPropagator
richiede per funzionare!
Ho sfruttato le facility che la Base mi mette a disposizione! ;)
private void propagateRolePermissionsToModelloCustom(ActionRequest actionRequest,
String className, String primKey, long[] roleIds) throws PortalException {
long fileEntryId = GetterUtil.getLong(primKey);
ModelloCustom modelloCustom = modelloCustomLocalService.fetchByFileEntryId(fileEntryId);
if (Validator.isNotNull(modelloCustom)) {
for (long roleId : roleIds) {
propagateRolePermissions(actionRequest, roleId, DLFileEntry.class.getName(),
fileEntryId, ModelloCustom.class.getName(), modelloCustom.getPrimaryKey());
}
}
}
public void propagateRolePermissionsToFileEntry(ActionRequest actionRequest,
String className, String primKey, long[] roleIds) throws PortalException {
ModelloCustom modelloCustom = modelloCustomLocalService.getModelloCustom(GetterUtil.getLong(primKey));
if (modelloCustom.getFileEntry() > 0) {
for (long roleId : roleIds) {
propagateRolePermissions(actionRequest, roleId, ModelloCustom.class.getName(),
modelloCustom.getPrimaryKey(), DLFileEntry.class.getName(), modelloCustom.getFileEntry());
}
}
}
È qui che accade la magia! :)
La chiamata alla propagateRolePermissions
è chiaramente quella che sfrutto per far accadere la magia e, nemmeno a dirlo, è quella messa a disposizione dalla mia Base!
Cosa fa di magico questo metodo?
In pratica fa una cosa molto.. Pratica! :)
Si preoccupa di capire quali sono i permessi in comune tra le due entità (mappati nella tabella ResourceAction
) e poi li sincronizza! Tuto gratis, tutto trasparente e, soprattutto, tutto.. Che funziona! :)
Beh, anche per oggi abbiamo imparato qualche cosa di nuovo (cfr. "smettila di pensare che il custom sia l'unica via per perseguire l'obiettivo: su più di 1.000.000 di righe di codice ci sarà qualche cosa che fa già al caso tuo, no?:)") e siamo pronti a propagare permessi come se non ci fosse un domani! ;)
Alla prossima e divertitevi con Liferay 7.x, OSGi e... I vostri animali mitologici!! :)
A presto, ciao, J.
P.S. Stavo quasi per dimenticarmi!
Aggiungete questa riga nel vostro portal-ext.properties
altrimenti non funzionerà mai l'implementazione che avete appena fatto!
##
## Permissions
##
#
# Set the following to true to enable propagation of permissions between
# models.
#
# For example, when setting the permissions on a specific Wiki node, if you
# assign a role a permission (e.g. DELETE), then the assignment of that
# permission is also propagated to all Wiki pages that belong to that Wiki
# node.
#
# The actual logic of how permissions are propagated among models is
# specified per portlet. See liferay-portlet.xml's use of the element
# "permission-propagator".
#
# Env: LIFERAY_PERMISSIONS_PERIOD_PROPAGATION_PERIOD_ENABLED
#
permissions.propagation.enabled=true