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. 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!

  2. 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! :)

  3. 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.

     

  4. Ho avuto la necessità di dover recuperare il numero delle notifiche non lette tramite un servizio rest per poterne fare il polling via Javascript. Liferay purtroppo non mette a disposizione un api rest per le notifiche, per cui ho deciso di implementarla utilizzando il template BLADE "rest" messo a disposizione da Liferay DXP.

    Ho creato, da Eclipse, un nuovo progetto utilizzando il template "rest"

    Per prima cosa verifichiamo che nel  build.gradle ci siano le seguenti dipendenze:

     compile group: "javax.ws.rs", name:"javax.ws.rs-api", version:"2.0.1" compileOnly group: "org.apache.cxf", name: "cxf-core", version: "3.0.3" compileOnly group: "org.apache.cxf", name: "cxf-rt-frontend-jaxrs", version: "3.0.0" compileOnly group: "org.apache.cxf", name: "cxf-rt-rs-extension-providers", version: "3.0.3"

    Ora creaimo nella cartella resources una cartella configuration e creiamo questi due files:

    • com.liferay.portal.remote.cxf.common.configuration.CXFEndpointPublisherConfiguration-cxf.properties
    • com.liferay.portal.remote.rest.extender.configuration.RestExtenderConfiguration-rest.properties

    creaimo il CXFEndpointPublisherConfiguration-cxf.properties con queste property

     contextPath=/api extensions= authVerifierProperties=auth.verifier.PortalSessionAuthVerifier.urls.includes\=*

    e il RestExtenderConfiguration-rest con queste property

     contextPaths=/api jaxRsProviderFilterStrings= jaxRsServiceFilterStrings= jaxRsApplicationFilterStrings=

    Inserendo queste configurazioni direttamente da property potremo pubblicare i nostri servizi (in questo esempio risponderranno sul path /api) senza dover configurare nulla in control panel, a patto di aggiungere al bnd.bnd le seguenti informazioni

     Liferay-Configuration-Path: /configuration Include-Resource:configuration=src/main/resources/configuration

    A questo punto passiamo all'implementazione del codice vero e proprio

    Il template blade avrà creato già un OSGI component con all'interno dei metodi di esempio.

    Le annotation di JAX ci permettono di recuperare alcuni parametri direttamente dal contesto, ma purtroppo i parametri recuperabili di default non sono correlati a oggetti Liferay. Nel nostro specifico caso sarebbe utile poter recuperare l'utente direttamente dal contesto, senza che questo debba essere passato come parametro.

    Cercando un pò in rete ho trovato questo articolo che spiega come creare dei custom provider:

    https://web.liferay.com/it/web/user.26526/blog/-/blogs/rest-custom-context-providers

    Di seguito il custom provider che ho creato per recuperare l'utente loggato

     @Component(immediate = true, service = UserContextProvider.class) @Provider public class UserContextProvider implements ContextProvider<User> { @Override public User createContext(Message message) { try { return _portal.getUser( (HttpServletRequest)message.getContextualProperty( "HTTP.REQUEST")); } catch (PortalException pe) { if (_log.isWarnEnabled()) { _log.warn("Unable to get user", pe); } return null; } } private static final Log _log = LogFactoryUtil.getLog( UserContextProvider.class); @Reference private Portal _portal; }

    Implementata la seguente classe basterà referenziarla attraverso il metodo getSingletons()creato in automatico da blade

     @Override public Set<Object> getSingletons() { Set<Object> singletons = new HashSet<>(); singletons.add(_userContextProvider); singletons.add(this); return singletons; }

    Ora non ci rimane che esporre il nostro metodo per effettuare il count delle notifiche

     @GET @Path("/notificationCount") @Produces(MediaType.APPLICATION_JSON) public String countNotifications(@Context User user) { int notificationCount = _userNotificationEventLocalService.getArchivedUserNotificationEventsCount(user.getUserId(), false); JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); jsonObject.put("notificationCount", notificationCount); return jsonObject.toJSONString(); }

    Invochiamo da browser l'url che risponde sul path /o/api/<rest-path>/notificationCount e il gioco è fatto.

     

     

     

  5. Ciao a tutti!

    Questo è un post super rapido che tratta un tema sicuramente sentito tra chi sviluppa applicazioni web, ovvero "come posso trappare il caricamento della mia (pagina|portlet|singola portlet)?".

    La spiegazione è, come al solito, molto semplice e fornita dalle funzionalità standard di Liferay!

    Vediamoli nel dettaglio! ;)

    Come trappare il caricamento del DOM HTML (ad esclusione delle portlet)

    AUI().ready(
         /*
         This function gets loaded when all the HTML, not including the portlets, is
         loaded.
         */
     
         function() {
         }
    );

     

    Come trappare il caricamento di una singola portlet

    Liferay.Portlet.ready(
        /*
        This function gets loaded after each and every portlet on the page.
        portletId: the current portlet's id
        node: the Alloy Node object of the current portlet
        */

        function(portletId, node) {
        }
    );

    Come trappare quando tutte le portlet sono state caricate

    Liferay.on(
        'allPortletsReady',

        /*
        This function gets loaded when everything, including the portlets, is on
        the page.
        */

        function() {
        }
    );

    Grazie a questi frammenti di javascript potete ottenere l'effetto voluto! :)

    Spero d'esservi stato d'aiuto! :)

    Alla prossima, ciao, J.