bloggers bloggers

Jader Jed Francia
Messaggi: 58
Stelle: 0
Data: 16/11/19
Paolo Gambetti
Messaggi: 2
Stelle: 0
Data: 11/11/19
Katia Pazzi
Messaggi: 1
Stelle: 0
Data: 27/06/19
Marco Napolitano
Messaggi: 76
Stelle: 0
Data: 11/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

Sincronizzare permessi tra modelli differenti!

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

 

Commenti
Aggiungi Commento
Marco Napolitano
Nice job!
Inviato il 15/10/19 12.35.
Jader Jed Francia
Aggiungo un ulteriore dettaglio: questo meccanismo *non funziona* come i value object listener, quindi potete avere UN SOLO PermissionPropagator per TIPO di oggetto..
Cosa significa? Significa che se avete dei DlFileEntry collegati a due differenti oggetti, il PermissionPropagator che "ascolta" il DlFileEntry DEVE essere unico, altrimenti LR caricherà il primo che trova.. emoticon
Inviato il 18/10/19 5.46.
Marco Napolitano
Ma esattamente qual è l'evento che avvia il PermissionPropagator? E' forse il salvataggio dei permessi di una delle 2 entità?
Se l'evento è il salvataggio dei permessi, allora questa soluzione probabilmente non funziona su strutture non lineari.
Mi spiego meglio: supponiamo di essere nel caso "cartella di file" e "documenti al suo interno".
Se modifico i permessi della cartella, questi si propagano ai documenti (chiaramente il viceversa non avrebbe molto senso).
Ma se aggiungo un nuovo documento nella cartella cosa succede? I permessi vengono propagati lo stesso dalla cartella al documento? Se sì, come?
Inviato il 06/11/19 17.01.
Jader Jed Francia
Napo, dai!!! Non sei stato attento! ;)
Il PermissionPropagator viene registrato sul modello "da ascoltare"; successivamente, quando intercetta una modifica ai permessi di una istanza questo modello, si attiva e _propaga_.
Il caso d'uso che censisci tu, invece, è un'altra roba: è il concetto di "ereditarietà dei permessi" -che nel caso specifico della D&M è implementato OOTB come descrivi, per essere precisi- ma che serve a fare altro rispetto al PermissionPropagator.
Per essere espliciti, dove "Contenitore" è l'oggetto che "contiene" figli e "contenuto" sono i figli del "contenitore" (ad es. Folder -> contenitore, File -> contenuto):
- il PermissionPropagator viene utilizzato quando cambi i permessi sull'oggetto "contenitore" e devi propagare le modifiche ai suoi figli;
- l'Ereditarietà dei permessi tra "contenuto" e "contenitore", invece, è da gestire in maniera esplicita.

Spero d'aver risposto alla domanda; in caso contrario, come sempre ;), sono qui! ;)
Inviato il 07/11/19 7.39 in risposta a Marco Napolitano.