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. Alzi la mano chi ha dovuto inviare una mail con Liferay!

    Lo sappiamo, non è poi un'operazione così difficile, tuttavia ci sono sempre alcuni requisiti comuni ad ogni mail:

    • preparare un template HTML
    • salvare il template HTML da qualche parte
    • sostituire tutti i placeholder
    • inviare la mail

    In questo post voglio fare un pò di chiarezza sui vari punti, così al prossimo invio di una mail sapere dove andare a leggere!

    Come prima cosa aggiungiamo nel file build.gradle tutte le dipendenze che servono:

     compileOnly group: "com.liferay", name: "com.liferay.petra.content" compileOnly group: "com.liferay", name: "com.liferay.petra.mail" compileOnly group: "com.liferay", name: "com.liferay.petra.string"

    Preparare un template HTML e salvarlo nel bundle

    Il template HTML alla fine non è altro che una stringa ma se pensiamo al testo di una mail, potrebbe essere molto lungo e quindi scomodo da salvare in una costante; sarebbe molto meglio poterlo salvare in un file di testo.

    Pertanto creiamo una sottocartella templates (chiamatela pure come volete) all'interno di src/main/resources  ed inseritegli un file template_body.tmpl (anche qui il nome è a vostra scelta) contenente il testo della mail fatto, ad esempio, nel modo seguente:

     Gentile [$FIRST_NAME$] [$LAST_NAME$], grazie per esserti registrato al sito. Tutte le notifiche verranno spedite all'indirizzo <strong>[$EMAIL_ADDRESS$]</strong>. Lo staff

    Il testo è molto semplice ma serve solamente a mostrare come devono essere inseriti i placeholder all'interno del testo secondo lo standard Liferay: [$ seguito dal nome del placeholder (tutto maiuscolo senza spazi), il tutto chiuso da $].

    Con il template HTML abbiamo finito; viene salvato all'interno del bundle OSGi e quindi distribuito insieme al JAR.

    Sostituire tutti i placeholder

    La prima operazione da fare è recuperare il template HTML; per farlo bisogna usare una classe di portale passandogli il percorso del template HTML ed il classloader di una classe situata nel medesimo bundle del template stesso.

    Una volta recuperato il template si istanzia un oggetto MailTemplate che rappresenta appunto il template.

     String content = ContentUtil.get(this.getClassLoader(), "templates/template_body.tmpl"); MailTemplate mailTemplate =MailTemplateFactoryUtil.createMailTemplate(content, true);

    A questo punto è necessario configurare il contesto del template, ossia l'elenco di tutte le sostituzioni da fare sui placeholder; anche in questo caso utilizzeremo delle classi di portale.

     User user =... // recuperato da qualche parte MailTemplateContextBuilder mailTemplateContextBuilder = MailTemplateFactoryUtil.createMailTemplateContextBuilder(); mailTemplateContextBuilder.put("[$FIRST_NAME$]", user.getFirstName()); mailTemplateContextBuilder.put("[$LAST_NAME$]", user.getLastName()); mailTemplateContextBuilder.put("[$EMAIL_ADDRESS$]", user.getEmailAddress()); MailTemplateContext mailTemplateContext = mailTemplateContextBuilder.build();

    A questo punto possiamo finalmente sostituire i placeholder del testo:

     String body = mailTemplate.renderAsString(Locale.ITALY, mailTemplateContext);

    Il grosso del lavoro è finito, abbiamo il testo finale da poter inviare via mail ma questo è facile.

     MailEngine.send(from, to, subject, body, true);

    Enjoy!

  2. Quando è necessario selezionare una cartella della Document Library di Liferay, una pratica alternativa all'implementazione degli ItemSelector, e nello specifico dei FileItemSelector, è l'invocazione tramite javascript della procedura nativa di selezione cartella della Document Library di Liferay.

    Vediamo come fare in Liferay 7.0.

    Per prima cosa è necessario individuare quali sono i file che contengono le funzioni javascript che "invocano" la procedura di selezione della cartella.

    Il package jar contenente i sorgenti è: "com.liferay.document.library.web-3.0.16-sources.jar"

    All'interno del package nel percorso "\META-INF\resources\document_library\" si trovano i file .jsp di nostro interesse, nello specifico: "configuration.jsp" e "init.jsp"

    Dal file "configuration.jsp" possiamo estrapolare lo script javascript di nostro interesse e inserirlo nel file .jsp di nostro interesse:

    <aui:script>
                    AUI.$('#<portlet:namespace />selectFolderButton').on(
                        'click',
                        function(event) {
                            Liferay.Util.selectEntity(
                                {
                                    dialog: {
                                        constrain: true,
                                        destroyOnHide: true,
                                        modal: true,
                                        width: 600
                                    },
                                    id: '_<%= HtmlUtil.escapeJS(portletResource) %>_selectFolder',
                                    title: '<liferay-ui:message arguments="folder" key="select-x" />',
     
                                    <liferay-portlet:renderURL portletName="<%= portletResource %>" var="selectFolderURL"
                                        windowState="<%= LiferayWindowState.POP_UP.toString() %>">
                                        <portlet:param name="mvcRenderCommandName" value="/document_library/select_folder" />
                                        <portlet:param name="folderId" value="<%= String.valueOf(rootFolderId) %>" />
                                        <portlet:param name="ignoreRootFolder" value="<%= Boolean.TRUE.toString() %>" />
                                        <portlet:param name="showMountFolder" value="<%= Boolean.FALSE.toString() %>" />
                                    </liferay-portlet:renderURL>
     
                                    uri: '<%= HtmlUtil.escapeJS(selectFolderURL.toString()) %>'
                                },
                                function(event) {
                                    var folderData = {
                                        idString: 'rootFolderId',
                                        idValue: event.folderid,
                                        nameString: 'rootFolderName',
                                        nameValue: event.foldername
                                    };
     
                                    Liferay.Util.selectFolder(folderData, '<portlet:namespace />');
                                }
                            );
                        }
                    );
                </aui:script>
     
     

    Per poter utilizzare questo codice sarà necessario apportare qualche modifica, ma prima di tutto dovremo inserire nel nostro file .jsp il pulsante con nome "selectFolderButton" cui è associato l'evento di click.

    Ad esempio: 

    <aui:button name="selectFolderButton" value="choose-directory" /> 

    Nel codice javascript ci sono due variabili Java che vengono passate come parametri nella liferay-portlet:renderURL devono essere valorizzate: portletResource e rootFolderId.

    portletResource lo valorizziamo a:

    <% String portletResource = "com_liferay_document_library_web_portlet_DLPortlet"; %>

    Il valore impostato corrisponde alla costante DOCUMENT_LIBRARY della classe DLPortletKeys del pacakge com.liferay.document.library.constants.

    public class DLPortletKeys {
     
    public static final String DOCUMENT_LIBRARY ="com_liferay_document_library_web_portlet_DLPortlet";
     
    Utilizziamo direttamente il valore della costante passato come stringa per problemi di conflitti di librerie che si avrebbero utilizzando la classe DLPortletKeys.
     
    rootFolderId deve essere valorizzato al valore dell'id della folder di root, questo può essere impostato, ad esempio a "0" se vogliamo partire dalla root della Document Library, oppure a un valore ricavato dal contesto applicativo.
     
    Una volta cliccato il pulsante selectFolderButton si aprirà una pop-up window dalla quale sarà possibile esplorare l'albero delle cartelle e selezionare la cartella desiderata con il pulsante "Scegli".
    Una volta effettuata la selezione, nell'oggetto javascript folderData vengono ritornati i dati relativi alla cartella selezionata da UI: idValue, nameValue.

     

  3. Lavorando con Liferay ed il Service Builder vi sarà sicuramente capitato di dover gestire delle relazioni N-N; come sappiamo, all'interno del file service.xml, è possibile utilizzare l'attributo mapping-table per definire la relazione N-N demandando al Service Builder tutto il lavoro sporco.

    Ciò significa che il codice generato dal Service Builder conterrà tutti i metodi per leggere e scrivere record nella tabella di join; non c'è nulla di magico in questo, semplicemente vengono utilizzate determinate classi che si occupano di tutto quanto.

    Tuttavia potrebbe capitarvi di dover gestire una relazione N-N a mano, senza poter usare l'attributo mapping-table e quindi potenzialmente perdendo tutti gli automatismi. Per fortuna non è così, se sai cosa fare; esiste infatti l'interfaccia TableMapper che possiamo utilizzare direttamente senza problemi.

    Supponiamo di dover gestire la relazione N-N tra autorie libri; all'interno del service.xml avremo pertanto definito 3 entità:

    1. Author, con chiave primaria authorId;
    2. Book, con chiave primaria bookId;
    3. Author_Book, con chiave primaria composta da authorId e bookId.

    Ovviamente il Service Builder ci mette già a disposizione tutti i metodi di base ma quello che vogliamo è avere un metodo che ci restituisca l'elenco degli autori a partire dal libro e l'elenco dei libri a partire dall'autore; infatti sebbene si possano definire dei finder all'interno dell'entità Author_Book (findByAuthorId e findByBookId), questi restituiranno comunque una lista di oggetti Author_Book e non una lista di Author o di Book.

    Vediamo quindi come il TableMapper possa aiutarci; apriamo quindi la classe AuthorLocalServiceImpl e definiamo il seguente metodo:

     public List<Autor> getBookAuthors(long bookId, int start, int end, OrderByComparator comparator) throws SystemException { TableMapper tableMapper = TableMapperFactory.getTableMapper( Author_BookImpl.TABLE_NAME, "authorId", "bookId", authorPersistence, bookPersistence); return tableMapper.getLeftBaseModels(bookId, start, end, comparator); }

    Come vedete è veramente semplicissimo; prima di tutto si ottiene un'istanza del TableMapper attraverso la factory. I parametri della factory sono i seguenti:

    1. Nome della tabella di join (abbiamo già una bella costante);
    2. Nome della chiave primaria della tabella di sinistra della relazione N-N;
    3. Nome della chiave primaria della tabella di destra della relazione N-N;
    4. Classe di persistenza della tabella di sinistra della relazione N-N;
    5. Classe di persistenza della tabella di destra della relazione N-N.

    A questo punto possiamo semplicemente invocare uno dei tanti metodi forniti da TableMapper, per leggere o scrivere dati:

    • getLeftBaseModels, restituisce l'elenco delle entità di sinistra associate allo specifico elemento di destra(ossia l'elenco degli autori in base al libro);
    • getRightBaseModelsrestituisce l'elenco delle entità di destra associate allo specifico elemento di sinistra (ossia l'elenco dei libri in base all'autore);
    • addTableMapping, aggiunge un elemento alla relazione N-N;
    • deleteTableMapping, rimuove un elemento dalla relazione N-N;
    • ...

    Chiaramente farete poi una cosa analoga nella classe BookLocalServiceImpl, ma ve lo lascio come esercizio.

    Ok, ditemi se non è una figata!

  4. Attenzione! Il post non è proprio su Liferay DXP ma riguarda più Hibernate! Però, siccome è un tema che si potrebbe affrontare anche negli sviluppi su Liferay lo censisco qui, sperando che possa tornare utile a qualcuno! ;)

    Come ha già spiegato Napo in un post di qualche tempo fa, è possibile (ovviamente) creare custom query su Liferay 7 usando i c.d. Finder.

    Oggi però, mentre come al solito ero intento nel non far nulla :), un collega di una Università per la quale abbiamo realizzato un portale mi ha contattato chiedendomi una cosa!

    Qui lo stack della chat Telegram dove mi è arrivata la richiesta:

     Sandro Zacchino, [31.07.18 13:22] Ciao Jader, ti faccio una domanda da niubbo Sandro Zacchino, [31.07.18 13:23] ho realizzato una custom query in una classe Finder query.append("SELECT a.*, ea.* ") .append(" FROM " + DB_PREFIX + "Azienda a ") .append(" INNER JOIN " + DB_PREFIX + "Evento_Azienda ea ON ") .append(" ea.aziendaId = a.aziendaId ") .append(" AND ea.companyId = a.companyId ") .append(" AND ea.groupId = a.groupId ") .append(" WHERE 1=1 ") .append(" AND ea.eventoId = " + String.valueOf(eventoId)) Sandro Zacchino, [31.07.18 13:24] di seguito aggiungo ad a il suo modelImpl ad ea il suo...tutto funziona e visualizzo i risultati eccetto che per un particolare Jader Francia, [31.07.18 13:25] <suspance> Sandro Zacchino, [31.07.18 13:25] sia a che ea hanno un campo email il cui contenuto però è diverso. Succede però che ea.getEmail() (l'ho scritto in modo breve giusto per capirci) mi restituisca la mail di a

    Occavolo!

    Questa è nuova! Ho pensato. Non incollo lo stack di baggianate che gli ho detto di provare :) ma alla fine Sandro ha risolto da solo, elegantemente, il problema!

    Leggendo la documentazione di Hibernate, Sandro ha trovato nel paragrafo "16.1.4. Returning multiple entities" la soluzione al problema!

    Basta mettere tra {} gli alias della query e tutto funzionerà!

    Cito dalla documentazione di Hibernate:

    The following form is not vulnerable to column name duplication:

    sess.createSQLQuery("SELECT {cat.*}, {mother.*}  FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
     .addEntity("cat", Cat.class)
     .addEntity("mother", Cat.class)

    Grazie mille a Sandro (vero ghost writer di questo post ;)) per aver condiviso la soluzione e avermi permesso di fare un post in più per scalare la classifica e cercare di raggiungere Napo!! :)

    A presto e buon divertimento con le query custom, i nomi di colonna duplicati, hibernate e Liferay DXP! :)

  5. Liferay 7 mette a disposizione alcuni task gradle che possono venirci utili per l'inizializzazione del bundle e per crearne una distrubuzione per i vari ambienti.

    Per prima cosa da eclipse creiamo il workspace Liferay.

    Modifichiamo tramite un editor di testo il file gradle.properties andando a modificare la seguente property

    liferay.workspace.bundle.url

    mettendo come valore il link alla versione che vogliamo utilizzare

    Es:

    liferay.workspace.bundle.url=https://cdn.lfrs.sl/releases.liferay.com/portal/7.0.4-ga5/liferay-ce-portal-tomcat-7.0-ga5-20171018150113838.zip

    A questo punto siamo pronti per inizializzare il bundle

    Possiamo notare come all'interno del nostro workspace sia presente una cartella configs, che contiene tutte le configurazioni suddivise per tipologia di ambiente. La configurazione di default impostata nel gradle.properties è local

    liferay.workspace.environment=local

    Lanciamo il seguente comando per inizializzare il bundle

    gradlew initBundle -Pliferay.workspace.environment=local

    Possiamo non espicitare l'ambiente (in questo caso prenderà il default) o cambiarlo. Il task si connetterà al repository specificato e andrà a scaricare e configurare il nostro bundle nelle cartella bundle del nostro workspace (anche questa è modificabile da build.gradle)

    Ora invece vorremmo preparare uno zip/tar contenente tutto il nostro bundle comprensivo di tutti i moduli (che stanno sotto modules) buildati e già deployati

    gradlew distBundle[Zip|Tar] -Pliferay.workspace.environment=[ENVIRONMENT]

    Es:

    gradlew distBundleTar -Pliferay.workspace.environment=local

    Al termine dell'esecuzione troveremo nella cartella build del nostro workspace il tar/zip di tutto il nostro bundle comprensivo di moduli.