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. Recentemente mi sono trovato a dover lavorare con il motore di indicizzazione di Liferay 7 (che ricordo essere ElasticSearch) per estrarre e visualizzare dati complessi di entità custom.

    Una delle esigenze che avevo era quella di ordinare i dati, cosa che mi aspettavo fosse banale; purtroppo ho avuto un pò di problemi ma alla fine sono arrivato ad una soluzione che ora vi illustro, sperando di farvi risparmiare tempo prezioso.

    Innanzitutto partiamo dall'indicizzazione delle entità custom, ossia dal metodo doGetDocument della nostra classe Indexer; se vogliamo che un campo sia ordinabile dobbiamo indicizzarlo attraverso uno di questi metodi:

    • addKeywordSortable
    • addTextSortable
    • ...

    Questi metodi fanno sì che, all'interno del documento da indicizzare, vengano creati 2 campi diversi: uno che si chiama come il campo stesso, ad esempio code, mentre l'altro si chiama code_String_sortable e viene salvato tutto in minuscolo. L'infisso _String_ dipende dal tipo di dato del campo.

    Dopodichè, all'interno della JSP, possiamo creare l'oggetto SearchContext che conterrà tutti i parametri di ricerca, tra cui anche i campi su cui ordinare:

     SearchContext searchContext = SearchContextFactory.getInstance(request);... searchContext.setSorts(SortFactoryUtil.getSort(MyEntity.class, "code", "asc"));

    La definizione di ogni singolo campo di ordinamento deve essere effettuata mediante il medoto SortFactoryUtil.getSort; questo passaggio è fondamentale perchè il metodo getSort analizza la struttura dell'entità per capire come si deve chiamare esattamente il campo di ordinamento e per farlo invoca il metodo doGetSortField della nostra classe Indexer.

    L'implementazione di default restituisce semplicemente il nome del campo stesso, pertanto sarà necessario sovrascrivere il metodo per gestire correttamente la nomenclatura:

     @Override protected String doGetSortField(String orderByCol) { if ("code".equals(orderByCol)) return "code_String_sortable"; return super.doGetSortField(orderByCol); }

    Buon divertimento!

  2. Tempo fa vi avevo già parlato di query custom con Liferay DXP (http://blog.d-vel.com/home/-/blogs/sql-custom-con-liferay-dxp) spiegando alcune piccole differenze rispetto alla versione 6.2.

    Oggi voglio affrontare un'altra problematica, ossia eseguire query SQL custom che restituiscono oggetti del core come ad esempio User, Layout, JournalArticle,...

    Supponiamo quindi di avere realizzato un plugin custom che referenzi la tabella degli utenti di Liferay e vogliamo eseguire una query che restituisca gli utenti che soddisfano determinati criteri:

     <sql id="com.test.service.persistence.TabellaFinder.findUsers"> <![CDATA[ SELECT u.* FROM User_ u, Tabella t WHERE t.campo1 = ? AND t.campo2 = ? AND u.userId = t.userId ORDER BY u.lastName, u.firstName ]]> </sql>

    La query è molto semplice così come lo sarebbe la classe finder da implementare:

     public List<User> findUsers(long param1, long param2, int start, int end, OrderByComparator<User> comparator) { Session session = null; try { session = openSession(); String sql = CustomSQLUtil.get(this.getClass(), FIND_USERS); sql = CustomSQLUtil.replaceOrderBy(sql, comparator); SQLQuery q = session.createSQLQuery(sql); q.addEntity("User_", UserImpl.class); QueryPos qPos = QueryPos.getInstance(q); qPos.add(param1); qPos.add(param2); return (List<User>) QueryUtil.list(q, getDialect(), start, end); } finally { closeSession(session); } }
    Ma qui iniziano subito i nostri problemi, perchè per mappare i campi di output della query sull'oggetto User di Liferay, bisogna invocare il metodo addEntity passando come parametro la classe *Impl del model; peccato che la classe UserImpl non sia accessibile nei plugin custom.
    Questo problema in realtà si può risolvere in fretta giocando un pò con il classloader e sostituendo la riga di codice con questa:
     q.addEntity("User_", PortalClassLoaderUtil.getClassLoader().loadClass("com.liferay.portal.model.impl.UserImpl"));
    Però se andiamo ad eseguire il nostro metodo custom del finder, il portale solleverà questa eccezione:
     org.hibernate.MappingException: Unknown entity: com.liferay.portal.model.impl.UserImpl

    Ma perchè? Per un problema legato al contesto in cui opera lo strato dei servizi.

    Vale a dire che il nostro metodo finder opera all'interno del contesto del plugin custom e quindi il metodo openSession() apre sì una sessione, ma confinata all'interno del nostro plugin nel quale l'entità UserImpl non esiste perchè fa parte del contesto di portale.

    Come fare allora? Beh, basta cambiare la modalità con cui si ottiene la sessione; dobbiamo restituire oggetti del core, quindi ci serve una sessione del core. Ecco quindi che il metodo del finder si modifica in questo modo:

     public List<User> findUsers(long param1, long param2, int start, int end, OrderByComparator<User> comparator) { Session session = null; SessionFactory sessionFactory = (SessionFactory) PortalBeanLocatorUtil.locate("liferaySessionFactory"); try { session = sessionFactory.openSession(); String sql = CustomSQLUtil.get(this.getClass(), FIND_USERS); sql = CustomSQLUtil.replaceOrderBy(sql, comparator); SQLQuery q = session.createSQLQuery(sql); q.addEntity("User_", PortalClassLoaderUtil.getClassLoader().loadClass("com.liferay.portal.model.impl.UserImpl")); QueryPos qPos = QueryPos.getInstance(q); qPos.add(param1); qPos.add(param2); return (List<User>) QueryUtil.list(q, getDialect(), start, end); } finally { sessionFactory.closeSession(session); } }

    In grassetto ho evidenziato le modifiche:

    • prima di tutto bisogna recuperare un'istanza della factory di portale, perchè dobbiamo usare quella anzichè quella predefinita del plugin;
    • poi dobbiamo aprire la sessione usando la factory appena recuperata;
    • poi referenziamo la classe *Impl attraverso il classloader di portale;
    • infine chiudiamo la sessione utilizzando sempre la factory recuperata sopra.

    Spero di avervi risparmiato ore di inutili fatiche!

    Ah, può essere che il meccanismo funzioni anche la versione 6.2.

  3. GRAZIE!

    Il mio grazie di cuore va a tutti i partecipanti che hanno animato, con passione e tantissima competenza, il secondo Liferay User Group Meeting Italiano!!

    Tante sono state le informazioni scambiate, tantissimo l'interesse per la tecnologia e, soprattutto, tantissima la partecipazione della comunità italiana sull'evento!

    Ma veniamo al resoconto della giornata, animata da speaker di altissimo livello, che hanno contribuito portando la loro passione e la loro competenza su temi davvero molto interessanti!

    Si parte con la solita scena di me che arrivo lungo.. :)

     

    Simone Zaninello su twitter immortala l'immancabile momento in cui io, ben oltre il limite massimo (come al solito.. ;)), preparo le slide della giornata!

    Poi però Antonio apre veramente la giornata!

    Per fortuna che c'è lui: Antonio Musarra!!

     

    Come al solito, con una competenza incredibile, Antonio ci racconta la sua esperienza di integrazione tra Liferay e Salesforce, facendo sembrare la cosa di una "semplicità" disarmante!!

    Antonio tiene banco per 45 minuti, raccontandoci per filo e per segno la sua esperienza d'integrazione e facendoci vedere codice, diagrammi e slide davvero di altissimo livello!

    D'altronde lo conosciamo bene: il suo blog è un punto di riferimento per tantissimi ed è uno dei contributori più attivi della community! Suo, infatti, è il supporto ai database enterprise di Liferay 7 CE, anche se con qualche trucco che non sveleremo qui! :) 

    Puntualizzo che Antonio ha anche raccontato la sua esperienza come beta tester di WeDeploy, il nuovo progetto Liferay per non-ho-ancora-ben-capito-cosa ;), ma sul quale Antonio ha già fatto diverse prove e test!

    Se volete maggiori info su questo tema lasciate un commento e sicuramente Antonio vi risponderà! :)

    Poi tocca a Simone..

     

    È poi la volta di Simone Zaninello, anche lui grandissimo esperto della piattaforma (non per niente è una delle punte di diamante di uno dei due platinum partner di Liferay in Italia.. :)), che tiene un talk super tecnico dove ci spiega come fare bene una cosa che sicuramente in tantissimi abbiamo cercato di fare (e magari, a martellate, ci siamo pure riusciti ;)), ma lui ci fa vedere come farlo nel modo giusto: integrare un modulo javascript all'interno della piattaforma!

    Anche qui massima attenzione della sala perché, come potete immaginare, quando uno bravo veramente ti spiega una cosa non puoi non ascoltarlo con la massima attenzione! :)

    Poi si va tutti a mangiare.. 

    Cosa ve lo dico a fare? :)

    Organizzo questo meeting a Bologna perché così, per una volta, non mi devo spostare per partecipare ad un evento ma poi, soprattutto, perché Bologna nel passato era definita in tre modi: la dotta (per via dell'Università), la turrita (e qui, dai, ci arrivate! :)) ed infine la grassa!

    Sapete benissimo a cosa mi riferisco, vero? :)

    Credo che questa foto rappresenti il 100% del mood della giornata!

    Ah, nella foto si stava dicendo tutti quanti "Grazie Pascal", perché il pranzo è stato offerto interamente da Liferay, e di questo gliene siamo stati tutti molto grati!! :)

    Dopo pranzo si ricomincia con Marco!

    Anche se siamo andati un filino lunghi sui tempi :), rifocillati da un lauto e sanissimo pranzo :), abbiamo ascoltato tutti Marco Conzatti di ThreadSolutions, una delle aziende tech a me più care perché, oltre ad essere amici di vecchissima data (con loro abbiamo fondato mille milioni di anni fa il Liferay Italian Partner Ecosystem) sono imprenditori davvero bravi ed accorti!

    Anche se ora sono super specializzati su Mule ESB, su Liferay ci stanno ancora tanto dentro perché Marco ci ha spiegato come ha fatto a realizzare il plugin per la TwoWay Authentication che è disponibile anche sul marketplace di Liferay!

     

    Aggiungo che le slide di Marco, senza volerne ad Antonio e Simone, erano davvero fighissime! Nick mi ha dato il link al sito da dove hanno preso il template, lo condivido perché tutti allo user group meeting hanno fatto i complimenti e quindi mi sembra una bella cosa da fare! :)

    Una nota di colore: Nick mi ha condiviso il link e come oggetto ha usato "Template Slide Fighissimi".. Giusto per dirvi quanto era alta la hype sul tema! :)

    E poi è toccato a lui.. LEX!!!

    Ormai immancabile allo user group meeting Lex di Firelay ci ha fatto un talk sul suo nuovo progetto super segreto che svelerà alla DevCon di quest'anno nella sua Amsterdam: siete ancora in tempo a prendere i biglietti perché pare che le novità quest'anno saranno davvero tantissime!! :)

    Io non ci vado perché da Amsterdam mi sono auto bandito (e chi mi conosce anche solo superficialmente sa perché.. ;)), però ci mando alcuni colleghi perché davvero: quest'anno sarà moooolto interessante partecipare, vero Marco Leo?? :)

    E poi ci siamo salutati..

     

    Ci siamo dati appuntamento all'anno prossimo, con rinnovato entusiasmo, dopo esserci divisi i gadget che Liferay ci ha voluto mandare!!

    Il mio grazie speciale e di cuore va però a tutti voi: agli speaker che hanno reso questo evento davvero magico, a tutti quelli che sono intervenuti condividendo e confrontandosi e anche a tutti quelli che non sono potuti venire (e che ci sono mancati tanto), come Denis, che aveva un impegno personale ma che, me ne sincererò personalmente, l'anno prossimo sarà sicuramente sul palco con noi!! :)

    Un grazie grande come una casa di cuore a tutti: è stato bello, interessantissimo e anche molto divertente; vi rimando al prossimo anno, sempre più numerosi e carichi, perché è così che facciamo qui a Bologna: ogni occasione è buona per fare festa e mangiare tutti insieme!!

    Arrivederci all'anno prossimo, ciao! :)

    Slide dell'evento

    Queste sono le slide che i relatori mi hanno rilasciato, tutte pronte per essere scaricate da voi:

     

  4. Vi è mai capitato di dover gestire l'avatar dell'utente da una portlet custom? Se lo avete fatto e non ci siete saltati fuori ecco come fare con poco sforzo.

    Innazitutto diciamo che esiste una comodissima taglib di portale che fa già tutto (o quasi) quello che serve: liferay-ui:logo-selector. L'unica difficoltà è capire come configurarla correttamente.

    Tutto il codice da scrivere andrà inserito unicamente all'interno della JSP; supponiamo quindi che:

    • user sia l'utente corrente (tipicamente ottenibile mediante themeDisplay.getUser());
    • selUser sia l'utente di cui gestire l'avatar.
     <c:choose> <c:when test='<%=UsersAdminUtil.hasUpdateFieldPermission(permissionChecker, user, selUser, "portrait") %>'> <liferay-portlet:renderURL plid="<%=PortalUtil.getControlPanelPlid(renderRequest) %>" portletName="<%=PortletKeys.MY_ACCOUNT %>" refererPlid="<%=themeDisplay.getPlid() %>" var="editUserPortraitURL" windowState="<%=LiferayWindowState.POP_UP.toString() %>" > <liferay-portlet:param name="struts_action" value="/my_account/edit_user_portrait" /> <liferay-portlet:param name="redirect" value="<%=PortalUtil.getCurrentURL(request) %>" /> <liferay-portlet:param name="p_u_i_d" value="<%=String.valueOf(selUser.getUserId()) %>" /> <liferay-portlet:param name="portrait_id" value="<%=String.valueOf(selUser.getPortraitId()) %>" /> </liferay-portlet:renderURL> <liferay-ui:logo-selector currentLogoURL="<%=selUser.getPortraitURL(themeDisplay) %>" defaultLogoURL="<%=UserConstants.getPortraitURL(themeDisplay.getPathImage(), selUser.isMale(), 0) %>" editLogoURL="<%=editUserPortraitURL %>" imageId="<%=selUser.getPortraitId() %>" logoDisplaySelector=".user-logo" /> <script type="text/javascript"> function _<%= PortletKeys.MY_ACCOUNT %>_changeLogo(url) { <portlet:namespace />changeLogo(url); } </script> </c:when> <c:otherwise> <img alt='<liferay-ui:message key="portrait" />' src="/<%=selUser.getPortraitURL(themeDisplay) %>" /> </c:otherwise> </c:choose>

    Innanzitutto verifichiamo che l'utente corrente (user) abbia il permesso di modificare il campo portrait dell'utente selUser; senza il permesso visualizziamo solamente l'immagine dell'utente.

    Dopodichè definiamo la URL che punta alla pagina di selezione, crop e modifica dell'immagine dell'utente; questa pagina è di portale e contiene tutto il codice necessario (Java e Javascript) allo scopo. Dobbiamo solamente crearla nel modo corretto, ossia facendola puntare principalmente alla portlet MY_ACCOUNT come indicato; tutti gli altri parametri sono necessari e sono stati ricavati analizzando i sorgenti di portale.

    Quindi abbiamo inserito la taglib che innesca il procedimento di modifica dell'avatar; i parametri sono abbastanza ovvi e rappresentano i dati dell'avatar corrente dell'utente e della URL di modifica vista sopra.

    Alla fine di tutto abbiamo semplicemente ridefinito la funzione Javascript changeLogo; questo si rende necessario perchè la pagina di modifica dell'avatar opera nel contesto della portlet MY_ACCOUNT e quindi cerca di eseguire la funzione con il proprio namespace, ossia _2_changeLogo. Ma la taglib renderizza in pagina la medesima funzione ma con il namespace della nostra portlet custom; di conseguenza a runtime la funzione _2_changeLogo non esiste. Quindi tutto quello che dobbiamo fare è definire la funzione con il nome invocato dalla portlet MY_ACCOUNT che non fa altro che invocare la funzione con il namespace della portlet custom; funzione che, ricordo, esiste già in pagina in quanto creata dalla taglib.

    Buon avatar a tutti!

  5. Recentemente stavo lavorando ad un progetto che consisteva nell'esporre dei metodi dello strato remoto dei servizi per essere consumati da un client REST.

    Praticamente tutte le entità possedevano campi di tipo long che rappresentavano foreign key verso altre entità; pertanto all'interno delle varie classi *Impl dei modelli avevo definito altrettanti metodi getter per recuperare gli oggetti referenziati dalle foreign key. E fino a questo punto non c'era nulla di insolito...

    Invocando però i metodi REST esposti mi sono accorto che alcune entità venivano serializzate in modalità shallow ossia presentavano unicamente i campi definiti nel file service.xml; altre entità invece venivano serializzate in modailtà deep ossia presentavano anche il contenuto di tutti gli oggetti restituiti dai metodi custom che avevo definito nelle classi *Impl.

    L'unica differenza tra queste entità era che per le prime (modalità shallow) avevo definito remote-service="true" nel service.xml mentre per le seconde (modalità deep) avevo definito remote-service="false" dal momento che si trattava di semplici DTO che non dovevano avere uno strato di persistenza.

    Indagando un pò ho scoperto che se un'entità viene definita con remote-service="true" allora l'attributo json-enabled viene messo di default a true; viceversa rimane a false.

    Avere l'attributojson-enabled="true" significa che il Service Builder andrà ad annotare le classi*ModelImpl con@JSON(strict = true); questa annotation fa sì che solamente i campi dichiarati nelservice.xml vengano serializzati, facendo quindi una serializzazioneshallow. Peccato che nel caso opposto (ossiajson-enabled="false"), in cui l'entità non dovrebbe supportare la serializzazione JSON, la supporta ugualmente ed addirittura in modalitàdeep! Sarà un bug o una feature?

    Quindi come facciamo ad avere noi il controllo di quello che viene serializzato? Innanzitutto dobbiamo partire dal fatto che per essere serializzabile in formato JSON, un'entità deve avere json-enabled="true"; quindi le definiamo così:

     <entity name="Entity1" remote-service="true"...>... </entity> <entity name="Entity2" remote-service="false" json-enabled="true"...>... </entity>

    Facendo in questo modo, come abbiamo già visto sopra, otterremo una semplice serializzazione shallow; ma se volessimo spingerci oltre dobbiamo dire al Service Builder quali metodi custom includere nella serializzazione. Quindi se nella classe Entity1Impl abbiamo definito dei metodi custom, sarò sufficiente annotarli con @JSON(include = true) ed il risultato di tali metodi verrà serializzato in formato JSON insieme al resto dell'entità.

    Bella lì!