I cookie ci aiutano a fornire i nostri servizi.

Utilizzando tali servizi, accetti l'utilizzo dei cookie da parte nostra. Per saperne di piu'

Approvo

D'vel Blog

A partire dalla fine del 2010 lavoro come Liferay Expert presso l'azienda D'vel snc, per la quale scrivo spesso articoli tecnici sul blog.

Il blog è davvero molto utile e ricco di preziosi consigli; per chi fosse interessato l'indirizzo è il seguente: blog.d-vel.com.

Ecco un estratto degli ultimi articoli scritti.

blogdvel
  1.  

    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.

     

     

     

     

     

  2. 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...
  3. 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.
  4. 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!!

  5. Buongiorno a tutti!

    Eccomi di nuovo da voi per un post su Liferay 7.1 e le magie di OSGi!

    Anche oggi vorrei raccontarvi quello che i miei tanti animali mitologici mi hanno chiesto qualche tempo fa e come sono riuscito a risolvere il problema!

    Per farlo, però, come sempre vi devo raccontare il caso d'uso, così che l'implementazione possa essere quantomeno sensata.. :)

    Caso d'uso: codice centralizzato ma.. Specializzato per ogni modulo!

    Lo so: la cosa più assurda dei miei post sono i requisiti che i miei animali mitologici riescono a darmi! :)

    Però credetemi: quando me li danno me li motivano e, almeno sulla carta, sembra che abbiano pure un senso! 

    Nello specifico, questa volta, quello che mi hanno chiesto è stato proprio.. Creare del codice che dovrà essere centralizzato (per ovvie ragione) ma che.. Debba essere specializzato per ogni modulo! Il motivo è semplice:

    • centralizzato: perché alla fine la logica è uguale in tutti i punti dove viene utilizzato;
    • specializzato: perché vogliono predisporsi al cambiamento; nel caso la logica standard non vada più bene in un modulo, vogliono semplicemente cambiarne l'implementazione in quel modulo e tutto deve continuare a funzionare.

    Come al solito, nemmeno a farlo apposta, tutti a me capitano.. :D

    Ora che abbiamo definito che i miei animali mitologici sono particolari :), andiamo a vedere come ho risolto questi due vincoli, sfruttando una delle caratteristiche di OSGi che usate spesso ma alla quale, magari, non avete mai prestato particolare attenzione..

    Coding time!

    Partiamo subito facendo vedere il codice che ho centralizzato; qui, ovviamente, non mi soffermo sul cosa, perché è abbastanza indifferente. Mi concentro di più sul come, che è la parte un po' più interessante dell'implementazione.

     package it.dvel.playground.search; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.search.SearchContext; import com.liferay.portal.kernel.search.filter.BooleanFilter; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.search.spi.model.query.contributor.ModelPreFilterContributor; import com.liferay.portal.search.spi.model.registrar.ModelSearchSettings; import it.dvel.playground.util.search.SearchContextConstants; import org.osgi.service.component.annotations.Component; import java.util.Date; @Component( immediate = true, property = "model.pre.filter.contributor.id=DateRangeFilter", service = ModelPreFilterContributor.class ) public class DateRangePreFilterContributor extends BaseDateRangePreFilterContributor { @Override public void contribute(BooleanFilter booleanFilter, ModelSearchSettings modelSearchSettings, SearchContext searchContext) { Date startDate = (Date) searchContext.getAttribute(SearchContextConstants.SEARCH_BY_FROM_DATE); Date endDate = (Date) searchContext.getAttribute(SearchContextConstants.SEARCH_BY_TO_DATE); if (Validator.isNotNull(startDate) && Validator.isNotNull(endDate)) { endDate = setTime(endDate, 23, 59); if (_log.isDebugEnabled()) { _log.debug("Start Date: " + startDate); _log.debug("End Date: " + endDate); } String field = (String) searchContext.getAttribute("it.dvel.playground.search.common.field.date.range"); addFilter(field, booleanFilter, startDate, endDate); } } private static final Log _log = LogFactoryUtil.getLog(DateRangePreFilterContributor.class); }

    Vi manca metà del mondo degli oggetti della modellazione, però avete l'unico che conta: l'oggetto che definisco centralizzato! Questo oggetto, che nel dettaglio serve a scatenare un filtro sulla ricerca attraverso l'indice nel caso siano presenti due date (lasciate stare il perché, dai.. :)), ha una particolarità: definisce un suo ID attraverso una property.

    Questa è la riga incriminata:

     property = "model.pre.filter.contributor.id=DateRangeFilter",

    Questa è la caratteristica interessante, e presto vedremo il perché!

    Injection di oggetti con.. Filtro!

    Il secondo requisito è quello della specializzazione: l'idea di base dei miei animali mitologici era quella di essere pronti al cambiamento! Siccome però, per definizione, gli animali mitologici sono come vogliamo vederli noi :), i miei erano pigri (almeno quanto me :)) e quindi volevano a tutti i costi che ogni modulo specializzato avesse:

    • una sua classe specifica per questo tipo di filtro;
    • che la classe specifica facesse allegramente uso dell'implementazione centralizzata.

    So che per i più questo, nel mondo del software ad oggetti, si chiama banalmente proxy, però qui la cosa si fa un po' più complicata, perché parliamo di Servizi rilasciati attraverso bundle differenti nel container OSGi.

    A questo punto.. Come fare a risolvere il requisito?

    Bhe, come dice il titolo di questo capitolo (e spoilerato poi anche dal dettaglio del capitolo precedente.. ;)), ci basterà solamente filtrare gli oggetti che ci vengono iniettati e il gioco sarà fatto!

    Ecco quindi come ho implementato l'oggetto all'interno del modulo specializzato, usando chiaramente il concetto di proxy sull'oggetto centralizzato!

     package it.dvel.playground.avvisi.search; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.search.SearchContext; import com.liferay.portal.kernel.search.filter.BooleanFilter; import com.liferay.portal.search.spi.model.query.contributor.ModelPreFilterContributor; import com.liferay.portal.search.spi.model.registrar.ModelSearchSettings; import it.dvel.playground.util.search.FieldConstants; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @Component( immediate = true, property = "indexer.class.name=it.dvel.playground.avvisi.model.Avviso", service = ModelPreFilterContributor.class ) public class AvvisiDateRangePreFilterContributor implements ModelPreFilterContributor { @Override public void contribute(BooleanFilter booleanFilter, ModelSearchSettings modelSearchSettings, SearchContext searchContext) { searchContext.setAttribute("it.dvel.plaground.search.common.field.date.range", FieldConstants.AVVISO_DATA_REDAZIONE); dateRangeFilterPreFilterContributor.contribute(booleanFilter, modelSearchSettings, searchContext); } @Reference(target = "(model.pre.filter.contributor.id=DateRangeFilter)") protected ModelPreFilterContributor dateRangeFilterPreFilterContributor; private static final Log _log = LogFactoryUtil.getLog(AvvisiDateRangePreFilterContributor.class); }

    La magia è tutta qui:

     @Reference(target = "(model.pre.filter.contributor.id=DateRangeFilter)") protected ModelPreFilterContributor dateRangeFilterPreFilterContributor;

    Nella Reference, infatti, io specifico indirettamente che vorrei un oggetto di tipo ModelPreFilterContributor però sono selettivo: non mi accontento di averne uno a caso ma voglio proprio quello che mi pare a me, quello che ha ID che vale DateRangeFilter.

    Ed è il container OSGi a fare la magia, popolandomi il field interno con l'oggetto specifico!

    Ovviamente questo tipo di implementazione sfrutta la caratteristica dell'iniezione selettiva degli oggetti fatta dal container OSGi, però, chiaramente, è basata su un assunto banale, ovvero che esista un solo ModelPreFilterContributor che ha quell'ID.

    Questo è un assunto molto forte, però è funzionale all'esempio che volevo mostrarvi!

    Adesso la palla è nel vostro campetto: se qualcuno ha idea di come rendere più robusta l'implementazione può scriverlo nei commenti, così da confrontarci e crescere tutti insieme, ovviamente migliorandoci! :)

    Fino ad allora, come al solito, divertitevi e fatemi sapere se vi torna tutto quello che ho scritto!

    Se avete domande o dubbi, potete scriverli nei commenti e sarò / saremo ben lieti di aiutarvi!!

    Buon divertimento a tutti con OSGi, Liferay 7.1 e i filtri sulle iniezioni degli oggetti! :)

    Materiale utile alla discussione

    • Javadoc dell'implementazione di Filter nel core di OSGi
    • La RFC1960 che rappresenta come i filtri di LDAP vadano costruiti (sintassi e logica): comodo perché è da qui che sono derivati  i filtri sugli oggetti nel container OSGi