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. Torniamo a parlare di Alloy Editor per capire come abilitare una funzionalità molto utile ed, ahimè, molto necessaria: Incolla da Word.

    Già, perchè purtroppo le persone hanno la brutta abitudine di scrivere su Word (mettendo font, grassetti, allineamenti, rientri,...) e poi copiare il testo direttamente all'interno dell'editor del sito web, con il risultato che la resa grafica è terribile ed i metadati nascosti che vengono copiati sono un'inutile infinità.

    Per fortuna i moderni editor HTML (tra cui CKEditor) sono in grado di gestire questa cosa, consentendo di incollare il testo di Word depurandolo da tutto quello che non serve. Anche Alloy Editor è in grado di farlo, ma bisogna configurarlo opportunamente.

    Per i dettagli tecnici (le dipendenze,...) vi rimando alla lettura del mio precedente articolo Allineamento del testo con Alloy Editor, adesso mi limiterò a farvi vedere direttamente il componente OSGi che dovete implementare.

     @Component(property = { "editor.name=alloyeditor", "javax.portlet.name=" + JournalPortletKeys.JOURNAL, "javax.portlet.name=" + PortletKeys.USER_PROFILE, "service.ranking:Integer=100" }, service = EditorConfigContributor.class) public class PasteFromWordEditorConfigContributor extends BaseEditorConfigContributor { @Override public void populateConfigJSONObject( JSONObject jsonObject, Map<String, Object> inputEditorTaglibAttributes, ThemeDisplay themeDisplay, RequestBackedPortletURLFactory requestBackedPortletURLFactory) { String extraPlugins = jsonObject.getString("extraPlugins"); if (Validator.isNotNull(extraPlugins)) extraPlugins += StringPool.COMMA; extraPlugins += "ae_uibridge,ae_buttonbridge,pastefromword,clipboard,dialog,dialogui"; jsonObject.put("extraPlugins", extraPlugins); JSONObject toolbars = jsonObject.getJSONObject("toolbars"); if (toolbars != null) { JSONObject add = toolbars.getJSONObject("add"); if (add != null) { JSONArray buttons = add.getJSONArray("buttons"); buttons.put("PasteFromWord"); } } } }

    In questo caso l'elemento chiave è extraPlugins; infatti prima di poter aggiungere il pulsante, bisogna abilitare alcuni plugin di CKEditor e questo si fa semplicemente aggiungedoli alla lista di quelli già presenti nell'oggetto di configurazione; se siete pignoli potete controllare se ci sono doppioni...

    Una volta fatto questo potete aggiungere il pulsante, che si chiama PasteFromWord, all'interno della toolbar add; questa toolbar, per intenderci, è quella contrassegnata dal grande simbolo + sulla sinistra, la stessa che consente di aggiungere immagini, tabelle,... Infatti, in questo caso, non dobbiamo modificare il testo selezionato ma agire direttamente sul contenuto dell'editor, nel punto in cui si trova il cursore.

    Per maggiori dettagli consultare:

  2. Liferay 7 introduce tante novità tra cui Alloy Editor, il nuovo e potente editor HTML derivato da CKEditor.

    Tutto molto bello ed interessante ma chi di voi ha avuto modo di utilizzarlo si sarà sicuramente chiesto come fare per allineare il testo.

    Perchè mi consente di inserire tabelle, cambiare lo stile ma non allineare il testo? E' impossibile che Alloy Editor non consenta di farlo...

    Infatti è vero, Alloy Editor può farlo ma bisogna scrivere qualche riga di codice per abilitare i pulsanti; innanzitutto aggiungete questa dipendenza nel vostro file build.gradle:

     compileOnly group: "com.liferay", name: "com.liferay.journal.api", version: "2.9.2"

    Dopodichè aggiungete la seguente classe Java al vostro bundle web:

     @Component(property = { "editor.name=alloyeditor", "javax.portlet.name=" + JournalPortletKeys.JOURNAL, "service.ranking:Integer=100" }, service = EditorConfigContributor.class) public class TextAlignmentEditorConfigContributor extends BaseEditorConfigContributor { @Override public void populateConfigJSONObject( JSONObject jsonObject, Map<String, Object> inputEditorTaglibAttributes, ThemeDisplay themeDisplay, RequestBackedPortletURLFactory requestBackedPortletURLFactory) { JSONObject toolbars = jsonObject.getJSONObject("toolbars"); if (toolbars != null) { JSONObject toolbarStyles = toolbars.getJSONObject("styles"); if (toolbarStyles != null) { JSONArray selections = toolbarStyles.getJSONArray("selections"); if (selections != null) { for (int i = 0; i < selections.length(); i++) { JSONObject selection = selections.getJSONObject(i); if (selection.has("name") && selection.getString("name").equals("text")) { JSONArray buttons = selection.getJSONArray("buttons"); if (buttons != null) { buttons.put("paragraphLeft"); buttons.put("paragraphCenter"); buttons.put("paragraphRight"); buttons.put("paragraphJustify"); } break; } } } } } } }

    La classe rappresenta un componente di tipo EditorConfigContributor che è l'elemento adibito a modificare la configurazione dell'editor HTML; il componente possiede alcune proprietà attraverso le quali è possibile fare modifiche molto granulari:

    • editor.config.key, è una proprietà ripetibile che rappresenta lo specifico campo del form in cui modificare la configurazione (contentEditor, summaryEditor,...);
    • editor.name, è una proprietà ripetibile che rappresenta il tipo di editor di cui modificare la configurazione (alloyeditor, ckeditor,...);
    • javax.portlet.name, è una proprietà ripetibile che rappresenta il nome della portlet in cui modificare la configurazione (può essere anche una portlet custom);
    • service.ranking:Integer, rappresenta la priorità del componente (potrebbero essercene più di uno da eseguire).

    La nostra classe estende poi la superclasse BaseEditorConfigContributor che si occupa di tutto il lavoro e ci obbliga ad implementare unicamente il metodo populateConfigJSONObject; questo metodo possiede alcuni parametri tra cui jsonObject che rappresenta la configurazione corrente dell'editor HTML.

    Tutto quello che abbiamo fatto nel codice è stato recuperare la sezione della configurazione che contiene i pulsanti ed aggiungere in fondo quelli adibiti all'allineamento del testo. Ecco quello che otteniamo:

    Attenzione perchè non abbiamo implementato l'allineamento del testo, quello già esisteva in Alloy Editor con tanto di icone; abbiamo solo aggiunto i pulsanti con il loro nome specifico.

    Per maggiori dettagli consultare:

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

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

  5. 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: