Cookies help us deliver our services.

By using our services, you agree to our use of cookies. Learn more

I understand

D'vel Blog

By the end of the 2010 I work as Liferay Expert for D'vel snc, for which I often write technical articles on its blog.

This blog is really interesting and full of articles; if you're curious, this is the link: blog.d-vel.com.

Here is a list of the latest articles written.

blogdvel
  1. 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 chenon 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

     

  2.  

    Quando si desidera visualizzare un contenuto HTML all'interno di un report PDF, TIBCO JasperSoft Studio

    offre due possibilità:

    1- impostare Markup/HTML in fase di design del template .jrxml;

    2- utilizzare HTMLComponent.

    La soluzione 1, non richiede di importare librerie aggiuntive e quindi è molto veloce,ma l'insieme dei tag

    HTML che  vengono convertiti correttamente in PDF è molto ridotto,ed in particolare tag di usomolto

    comunecome <strong>, <style>, <em> e <img> vengono ignorati.

    La soluzione 2 è un poco più articolata, ma offre l'indubbio vantaggio di convertire correttamente un

    numeromaggioredi tag HTML.

    HTMLComponent è una libreria prodotta da JasperSoft, che è stata ed è ancora ampiamente utilizzata,

    cometestimoniano i numerosi thread di discussione reperibili online.

    Al momento, però, non viene distribuita dai repository Maven per il download e non è disponibile sulla

    palettedeglistrumenti di TIBCO JasperSoft Studio 6.3.1.final.

    Tuttavia JasperSoft Studio deve continuare a gestirla per ragioni di retrocompatibilità, e quindi, di fatto

    quandonel template .jrxml trova questo codice:

            <bandheight="742">
            <componentElement>
                <reportElementx="0"y="0"width="555"height="742"/>
                <hc:htmlxmlns:hc="http://jasperreports.sourceforge.net/htmlcomponent"

    xsi:schemaLocation="http://jasperreports.sourceforge.net/htmlcomponent

    http://jasperreports.sourceforge.net/xsd/htmlcomponent.xsd"

    scaleType="RetainShape"horizontalAlign="Left"verticalAlign="Top">
                    <hc:htmlContentExpression><![CDATA[$P{myHtmlContent}]]></hc:htmlContentExpression>
                </hc:html>
            </componentElement>
        </band>

    JasperSoft Studio è in grado di mostrare tutte le properties dell 'HTMLComponent in formato visuale e

    consente di modificarle in modo assistito.

    In aggiunta, le versioni più recenti di JasperSoft Studio, come la 6.3.1 in questione, consentono di utilizzare

    HTMLComponent all'interno del tool di palette 'Generic Element' indicando i seguenti valori.

    Generic Type Name:htmlelement
    Generic Type Namespace:http://jasperreports.sourceforge.net/jasperreports/html

    In questo modo tutte le proprietà e i parametri possono essere visualizzate all'interno del pannello di

    propertiesdel 'Generic Element',anche se la loro modifica è meno assistita che nel caso precedente,

    trattandosi di un tool generico.

    Il risultato del nostro operato sarà, ad esempio, il seguente frammento sul template .jrxml.

               <bandheight="742">
           <genericElement><reportElementx="0"y="0"width="555"height="742"/>
                <genericElementTypenamespace="http://jasperreports.sourceforge.net/jasperreports/html"

    name="htmlelement"/>
                <genericElementParametername="htmlContent">
                    <valueExpression><![CDATA[$P{myHtmlContent}]]></valueExpression>
                </genericElementParameter>
                <genericElementParametername="scaleType">
                    <valueExpression><![CDATA["RetainShape"]]></valueExpression>
                </genericElementParameter>
                <genericElementParametername="verticalAlign">
                    <valueExpression><![CDATA["Top"]]></valueExpression>
                </genericElementParameter>
                <genericElementParametername="horizontalAlign">
                    <valueExpression><![CDATA["Left"]]></valueExpression>
                </genericElementParameter>
            </genericElement>
        </band>
     

    Comunque la si utilizzi, la libreria HTMLComponent, necessita al momento di essere configurata su Liferay

    mediante i seguenti passaggi.

    Queste istruzioni si riferiscono al progetto in cui è già installato e funzionante JasperReport, cioè in cui sono

    state dichiarate le dipendenze dalle librerie di JasperReport.

    1- Copiare nella cartellasrc/main/resources/lib tutti i jar contenuti in nella cartella 

       Jaspersoft Studio-6.3.1.final\configuration\org.eclipse.osgi\38\0\.cp\lib

    2- Aggiungere al build.gradle le dipendenze

       dependencies {

                      compileInclude  name: 'htmlcomponent'
                      compileInclude  name: 'core-renderer'
                      compileInclude  name: 'jtidy-r938'

                     //oppure, se si preferisce, 'jtidy' può essere scaricato dai repository Maven:

                    //compileInclude group: 'net.sf.jtidy', name: 'jtidy', version: 'r938'

       }

    3- Indicare sul build.gradle la locazione in cui si trovano queste librerie

       repositories {
           flatDir {
                 dirs 'src/main/resources/lib'
           }
       }
     
     

    4- Aggiungere queste esclusioni al file bnd.bnd

        !org.xhtmlrenderer.test,\
        !org.xhtmlrenderer.test.*,\

    A questo punto si è pronti per produrre i PDF partendo da un contenuto HTML.

    Va ricordato che tutto il contenuto HTML viene trasformato da questo componente in un' unica immagine,

    che pertanto potrebbe essere visualizzata diversamente rispetto ad un browser HTML e a seconda del

    formato di esportazione utilizzato.

     

     

     

     

     

  3. Correva l'anno 2009 quando una giovane Serena scriveva un interessante post sulla modalità con cui valorizzare i placeholder all'interno di un PDF; per chi se lo fosse perso vi invito a leggere il post originale.

    Sono passati ormai un pò di anni, i sistemi sono cambiati ma le esigenze sono rimaste le stesse ed infatti oggi mi sono ritrovato a dover fare la medesima cosa: valorizzare i placeholder all'interno di un PDF.

    Il post originale è stato di grande ispirazione ma purtroppo alcune librerie sono cambiate ed ho dovuto fare qualche modifica al codice; modifica che voglio condividere con voi... e con me stesso quando tra qualche mese dovrò rifarlo nuovamente!

    Innanzitutto io sto lavorando con Liferay 7.1.3 e di conseguenza la prima cosa da fare è configurare correttamente tutte le dipendenze. Nel mio caso utilizzerò solamente alcuni pacchetti forniti dalla libreria iText7; pertanto aggiungiamo le seguenti righe all'interno del file build.gradle:

     compileInclude group: "com.itextpdf", name: "kernel", version: "7.1.6" compileInclude group: "com.itextpdf", name: "forms", version: "7.1.6"

    Queste sono le uniche librerie che ci servono perchè l'unica cosa che dobbiamo fare è valorizzare i placeholder all'interno di un PDF che già esiste; la versione 7.1.6 è la più recente al momento in cui sto scrivendo questo post.

    In realtà queste librerie si portano dietro altre dipendenze, che a noi non servono, pertanto andiamo anche ad inserire le seguenti righe all'interno del file bnd.bnd:

     Import-Package: \ !org.bouncycastle.*,\ !org.slf4j.impl.*,\ *

    A questo punto facciamo un bel "gradle refresh" e deployamo tutto per verificare che le dipendenze siano tutte soddisfatte.

    Veniamo ora al codice da usare per valorizzare i placeholder e salvare il file di output; siccome stiamo parlando di file e di Liferay, diamo per scontato che il codice seguente sia inserito all'interno del metodo serveResource della nostra portlet in modo da consentirci di salvarlo.

     import com.itextpdf.forms.PdfAcroForm; import com.itextpdf.forms.fields.PdfFormField; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; // Recupero dalla Document Library del PDF contenente i placeholder FileEntry fileEntry =... // Creazione reader del PDF PdfReader reader = new PdfReader(fileEntry.getContentStream()); // File di output File file = FileUtil.createTempFile(); PdfWriter writer = new PdfWriter(file); // Creazione del documento PdfDocument pdfDocument = new PdfDocument(reader, writer); // Form contenente i campi del PDF da riempire PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDocument, true); form.setGenerateAppearance(true); // Recupero e valorizzazione dei placeholder Map<String, PdfFormField> fields = form.getFormFields(); fields.get("nome_campo_1").setValue("..."); fields.get("nome_campo_1").setValue("..."); fields.get("nome_campo_1").setValue("..."); // Valorizzare dei placeholder rimanenti // Rende i placeholder del PDF non modificabili form.flattenFields(); pdfDocument.close(); writer.close(); reader.close(); String fileName = fileEntry.getFileName(); // Download del file PortletResponseUtil.sendFile( resourceRequest, resourceResponse, fileName, FileUtil.getBytes(file), MimeTypesUtil.getContentType(file));
    Chiaramente potete anche decidere si salvare il file di output nella Document Libray anzichè farne il download...
  4. Fortunamente non ho animali mitologici da gestire come Jader, tuttavia però... ho Jader :)

    Oggi voglio parlare un pò di OSGi illustrandovi una soluzione molto elegante per risolvere un problema che potrebbe capitarvi.

    Il requisito iniziale, a parole, è molto semplice: rimuovere il link Password dimenticata dalla portlet di login nativa di Liferay 7.1 ed aggiungere un link custom che punti ad una pagina in cui è presente una portlet custom.

    In sostanza il risultato finale deve essere il seguente (sotto al pulsante Accedi):

    Soddisfare il primo requisito (rimuovere il link Password dimenticata) è semplice perchè si tratta di una configurazione da fare sull'istanza di Liferay.

    Invece per aggiungere un link custom la cosa è un pochino diversa; se fossimo sulla versione 6.2 avrei già iniziato a parlare di hook della JSP di portale e sinceramente il mio approccio iniziale era di fare qualcosa di analogo anche sulla 7.1. Così ho iniziato a cercare nei sorgenti di portale il punto in cui venivano gestiti i link ed ho scoperto che... non venivamo gestiti! Non esplicitamente almeno. Cioè?!

    Il file in cui vengono gestiti i link a fondo pagina nella portlet di login è il seguente:

     [SRC]\modules\apps\login\login-web\src\main\resources\META-INF\resources\navigation.jspf

    Ma al suo interno non ci sono link da modificare ma solamente taglib del tipo:

     <liferay-util:dynamic-include key="com.liferay.login.web#/navigation.jsp#post" />

    In pratica, per farla breve, tutti i link presenti vengono inclusi dinamicamente a runtime (dynamic-include per l'appunto); pertanto non bisogna modificare la portlet di login ma semplicemente realizzare un opportuno componente OSGi che venga caricato a runtime dalla taglib e si occupi di renderizzare il link custom.

    Non mi soffermo oltre e vi mostro qualche riga di codice!

     @Component(immediate = true, property = { "login.web.navigation.position=post", "service.ranking:Integer=100" }, service = PageInclude.class) public class MyNavigationPostPageInclude implements PageInclude { @Reference private LayoutLocalService _layoutLocalService; @Override public void include(PageContext pageContext) throws JspException { try { // La request serve sempre... HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); // Sito di riferimento long groupId = PortalUtil.getScopeGroupId(request); // Recupero la pagina a cui deve puntare il link custom Layout layout = _layoutLocalService .fetchLayoutByFriendlyURL(groupId, false, "/my-friendly-url"); if (layout != null) { /* * Se il testo del link non è una traduzione di portale, allora * è necessario recuperarla esplicitamente dal vostro bundle */ Locale locale = PortalUtil.getLocale(request); ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(locale, this.getClass()); String message = LanguageUtil.get( resourceBundle, "link-text-label"); // Creazione della render URL da associare al link custom LiferayPortletURL myURL = PortletURLFactoryUtil.create( request, PortletKeys.MY_CUSTOM_PORTLET, layout, PortletRequest.RENDER_PHASE); // Istanzio e configuro via codice la taglib aui:icon IconTag iconTag = new IconTag(); iconTag.setCopyCurrentRenderParameters(false); iconTag.setIconCssClass("icon-undo"); iconTag.setLocalizeMessage(false); iconTag.setMessage(message); iconTag.setUrl(myURL.toString()); iconTag.doTag(pageContext); } } catch (PortalException e) { throw new JspException(e); } } }
    Non so voi, ma io la trovo una soluzione estremamente elegante che evita di mettere mano ai sorgenti di portale.
  5. Buongiorno a tutti e ben trovati sul nostro blog! ;)

    Il post di oggi sarà super rapido e semplicissimo: di fatto, come spiego anche nell'intro, mi sono trovato a dover centralizzare taglib che vivevano in bundle differenti all'interno di un unico bundle centralizzato.

    Fin qui la cosa potrebbe sembrare semplice: fai un po' di copia / incolla / trascina (per i più sofisticati potrei definirlo refactor.. ;)) ma poi.. Fai deploy e scopri che non funziona più nulla!

    Come mai? Perché? Cosa NON ho copiato? Cosa ho sbagliato?

    Prima di farmi attanagliare da tutti questi dubbi, come spesso faccio :), mi sono buttato a capofitto nei sorgenti di Liferay 7.1 e ho scoperto che la cosa, da sistemare, era davvero semplice!

    bnd.bnd: come sempre, tutto parte da qui!

    Anche questa volta il titolo del capitolo non è molto evocativo ;), però è un periodo durante il quale la mia creatività è bassa.. Non me ne vogliate!

    Però il titolo è molto vero; infatti la prima cosa da fare è modificare il file bnd.bnd (che poi altro non è che il responsabile di quello che finisce nel manifest del nostro bundle OSGi) per dirgli:

    • che, ovviamente, fornisce come servizio delle taglib;
    • che abbiamo più di una taglib all'interno del bundle.

    Per fare questa magia, quindi, ho modificato così il mio bnd.bnd:

     Bundle-Name: dvel-util-taglib Bundle-SymbolicName: it.dvel.playground.util.taglib Bundle-Version: 1.0.0 Export-Package: \ it.dvel.playground.util.taglib.search.tag,\ it.dvel.playground.util.taglib.rubrica.tag Provide-Capability:\ osgi.extender;\ osgi.extender="jsp.taglib";\ uri="http://www.d-vel.com/tld/dvel";\ version:Version="${Bundle-Version}",\ osgi.extender;\ osgi.extender="jsp.taglib";\ uri="http://www.d-vel.com/tld/rubrica";\ version:Version="${Bundle-Version}" Web-ContextPath: /dvel-taglib

    La parte interessante è chiaramente quella che riguarda la property Provide-Capability; a questa, infatti, faccio corrispondere una lista di osgi.extender dove, per ognuno di essi, specifico la uri della singola taglib.

    Fatto questo il problema è risolto! Ora non ci resta che usare il nostro modulo e quindi le nostre taglib!

    Come fare? Semplice: nelle singole JSP dove ci servono, ci basterà importare la taglib (sempre nella init.jsp, mi raccomando!):

     <%@ taglib prefix="rubrica" uri="http://www.d-vel.com/tld/rubrica" %>

    e poi.. Utilizzarla:

     <rubrica:listUtil var="listUtil" />

    Come detto, questo post è super veloce e molto semplice.. Se sapevate come fare questa cosa!!

    Spero di avervi dato un buon suggerimento; se vi occorrono maggiori dettagli o siete semplicemente curiosi di conoscere qualche altro dettaglio su questo tema, come sempre, usate l'area dei commenti e saremo ben lieti di aiutarvi!!

    A presto e buon divertimento con Liferay 7.1, le taglib (centralizzate tutte in un unico modulo) e i casi funzionali degli animali mitologici più strani che ci siano! :)

    Alla prossima!!