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

     

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

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

  4. Ho avuto la necessità di integrare liferay 6.2 con un cas server per la gestione della login centralizzata. In questo post vedremo come autenticarci tramite facebook/linkedin attravero il cas.

    Per prima cosa scarichiamo il cas-overlay (nel mi caso quello relativo alla versione 5.0.4)
    Modifichiamo il build.gradle aggiungendo le seguenti dipendenze

      compile "org.apereo.cas:cas-server-support-ldap:${project.'cas.version'}"
    compile "org.apereo.cas:cas-server-support-pac4j-webflow:${project.'cas.version'}"

        
    La prima è relativa al supporto ldap (facoltativa) la seconda è relativa alle librerie pac4jche saranno utilizzate dal cas server per delegare l'autenticazione presso sistemi terzi (facebook/linkdin nel nostro caso)

    Una volta compilato e deployato il war, modifichiamo il file application.properties

    Aggiungiamo le properties relative a linkedin

     cas.authn.pac4j.linkedIn.id=<app_id> cas.authn.pac4j.linkedIn.secret=<app_secret> cas.authn.pac4j.linkedIn.fields=id,first-name,last-name,email-address,picture-url,positions cas.authn.pac4j.linkedIn.scope=r_basicprofile,r_emailaddress

    e quelle relative a facebook (i fields e gli scope compilati sono quelli approvati automaticamente alla creazione dell'app)

     cas.authn.pac4j.facebook.fields=id,name,first_name,last_name,gender,locale,languages,link,timezone,updated_time,verified,email,picture cas.authn.pac4j.facebook.id=<app_id> cas.authn.pac4j.facebook.secret=app_secret> cas.authn.pac4j.facebook.scope=email,public_profile,user_friends

    Settiamo le url del cas (verranno usate per le url di redirect)

     cas.server.name=https://<cas-server-host>:<port> cas.server.prefix=https://<cas-server-host>:<port>/cas-server

    Aggiungiamo la seguente property. Questa verrà utilizzata per far si che il principal venga popolato con un indicazione
    del sistema su cui ci siamo autenticati

    • cas.authn.pac4j.typedIdUsed=true

    Es:  org.pac4j.oauth.profile.facebook.FacebookProfile#<facebook_principal_id>

    Abilitiamo il cas (utilizzando il protollo cas3) a rilasciare gli attributi opzionali a sistemi terzi (nel nostro caso Liferay)

    cas.view.cas3.releaseProtocolAttributes=true

    e configuriamo gli attributi che andranno propagati di default

    cas.authn.attributeRepository.defaultAttributesToRelease=id,access_token,firstName,lastName,emailAddress,email,first_name,last_name,gender,locale    

    La configurazione del cas è terminata.

    Ora dobbiamo modificare Liferay per recepire queste configurazioni.
    Creiamo un ext plugin e aggiungiamo alla cartella ext-lib/portal

    1. cas-client-core-3.4.1.jar (la versione utlizzata dal cas 5.0.4, rinominandola il cas-client-core.jar)
    2. cas-client-integration-tomcat-v7-3.4.1.jar

    Facciamo override del cas filter modificando il metoto

     protected TicketValidator getTicketValidator(long companyId) throws Exception { TicketValidator ticketValidator = _ticketValidators.get(companyId); if (ticketValidator != null) { return ticketValidator; } String serverName = PrefsPropsUtil.getString(companyId, PropsKeys.CAS_SERVER_NAME, PropsValues.CAS_SERVER_NAME); String serverUrl = PrefsPropsUtil.getString(companyId, PropsKeys.CAS_SERVER_URL, PropsValues.CAS_SERVER_URL); String loginUrl = PrefsPropsUtil.getString(companyId, PropsKeys.CAS_LOGIN_URL, PropsValues.CAS_LOGIN_URL); Cas30ProxyTicketValidator cas30ProxyTicketValidator = new Cas30ProxyTicketValidator( serverUrl); Map<String, String> parameters = new HashMap<String, String>(); parameters.put("serverName", serverName); parameters.put("casServerUrlPrefix", serverUrl); parameters.put("casServerLoginUrl", loginUrl); parameters.put("redirectAfterValidation", "false"); cas30ProxyTicketValidator.setCustomParameters(parameters); _ticketValidators.put(companyId, cas30ProxyTicketValidator); return cas30ProxyTicketValidator; }

    in modo da ulizzare Cas30ProxyTicketValidatorinvece di Cas20ProxyTicketValidator. In questo modo anche Liferay utilizzerà il protocollo CAS3 per la verifica del ticket.

    Modifichiamo il processFilterdopo la parte di validazione

     Assertion assertion = ticketValidator.validate(ticket, serviceUrl); if (assertion != null) { AttributePrincipal attributePrincipal = assertion .getPrincipal(); login = attributePrincipal.getName(); if (_log.isInfoEnabled()) { _log.info("Cas Login string [" + login + "]"); } //in questa mappa sono contenuti tutti gli attributi propagati da cas Map<String, Object> attributes = attributePrincipal .getAttributes(); if (_log.isInfoEnabled()) { _log.info("Cas attributes for social login" + attributes); } //nel caso di una login direttamente da cas e non da social network if (Validator.isEmailAddress(login)) { session.setAttribute(WebKeys.CAS_LOGIN, login); } else { String[] casProfile = StringUtil.split(login, StringPool.POUND); String className = casProfile[0]; String id = casProfile[1]; ProfileBean profileBean = new ProfileBean(); String accessToken = MapUtil.getString(attributes, CasConstants.ACCESS_TOKEN); profileBean.setAccessToken(accessToken); profileBean.setId(id); if (StringUtil.equalsIgnoreCase(className, CasConstants.FACEBOOK_PAC4J_CLASSNAME)) { String emailAddress = MapUtil.getString(attributes, CasConstants.FACEBOOK_EMAIL); String firstName = MapUtil.getString(attributes, CasConstants.FACEBOOK_FIRST_NAME); String lastName = MapUtil.getString(attributes, CasConstants.FACEBOOK_LAST_NAME); String gender = MapUtil.getString(attributes, CasConstants.FACEBOOK_GENDER); String locale = MapUtil.getString(attributes, CasConstants.FACEBOOK_LOCALE); profileBean.setEmailAddress(emailAddress); profileBean.setFirstName(firstName); profileBean.setLastName(lastName); profileBean.setGender(gender); profileBean.setLocale(locale); profileBean.setType(CasConstants.FACEBOOK_TYPE); } else if (StringUtil.equalsIgnoreCase(className, CasConstants.LINKEDIN_PAC4J_CLASSNAME)) { String emailAddress = MapUtil.getString(attributes, CasConstants.LINKEDIN_EMAIL); String firstName = MapUtil.getString(attributes, CasConstants.LINKEDIN_FIRST_NAME); String lastName = MapUtil.getString(attributes, CasConstants.LINKEDIN_LAST_NAME); profileBean.setEmailAddress(emailAddress); profileBean.setFirstName(firstName); profileBean.setLastName(lastName); profileBean.setType(CasConstants.LINKEDIN_TYPE); } session.setAttribute(CasConstants.PROFILE_BEAN, profileBean); }

    In questo modo recuperiamo gli attributi propagati dal cas, popoliamo un oggetto che verrà messo in sessione e  che sarà utilizzato dalle classi di autologin per creare/loggare l'utente.

    Di seguito un'implementazione della FacebookAutoLogin

     @Override protected String[] doLogin(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession(); ProfileBean profileBean = (ProfileBean) session .getAttribute(CasConstants.PROFILE_BEAN); String[] credentials = new String[3]; if (Validator.isNotNull(profileBean) && profileBean.isFacebook()) { long companyId = PortalUtil.getCompanyId(request); User user = getUser(session, companyId); if (user == null) { user = _addUser(companyId, profileBean); } credentials[0] = String.valueOf(user.getUserId()); credentials[1] = user.getPassword(); credentials[2] = Boolean.FALSE.toString(); } return credentials; } protected User getUser(HttpSession session, long companyId) throws PortalException, SystemException { ProfileBean profileBean = (ProfileBean) session .getAttribute(CasConstants.PROFILE_BEAN); if (Validator.isNotNull(profileBean.getEmailAddress())) { session.removeAttribute(CasConstants.PROFILE_BEAN); return UserLocalServiceUtil.fetchUserByEmailAddress(companyId, profileBean.getEmailAddress()); } else { long facebookId = GetterUtil.getLong(profileBean.getId()); if (facebookId > 0) { return UserLocalServiceUtil.fetchUserByFacebookId(companyId, facebookId); } } return null; } private User _addUser(long companyId, ProfileBean profileBean) throws PortalException, SystemException { long creatorUserId = 0; boolean autoPassword = true; String password1 = StringPool.BLANK; String password2 = StringPool.BLANK; boolean autoScreenName = true; String screenName = StringPool.BLANK; String emailAddress = profileBean.getEmailAddress(); long facebookId = GetterUtil.getLong(profileBean.getId()); String openId = StringPool.BLANK; Locale locale = LocaleUtil.getDefault(); String firstName = profileBean.getFirstName(); String middleName = StringPool.BLANK; String lastName = profileBean.getLastName(); int prefixId = 0; int suffixId = 0; boolean male = Validator.equals(profileBean.getGender(), "male"); int birthdayMonth = Calendar.JANUARY; int birthdayDay = 1; int birthdayYear = 1970; String jobTitle = StringPool.BLANK; long[] groupIds = null; long[] organizationIds = null; long[] roleIds = null; long[] userGroupIds = null; boolean sendEmail = true; ServiceContext serviceContext = new ServiceContext(); User user = UserLocalServiceUtil.addUser(creatorUserId, companyId, autoPassword, password1, password2, autoScreenName, screenName, emailAddress, facebookId, openId, locale, firstName, middleName, lastName, prefixId, suffixId, male, birthdayMonth, birthdayDay, birthdayYear, jobTitle, groupIds, organizationIds, roleIds, userGroupIds, sendEmail, serviceContext); user = UserLocalServiceUtil.updateLastLogin(user.getUserId(), user.getLoginIP()); user = UserLocalServiceUtil .updatePasswordReset(user.getUserId(), false); user = UserLocalServiceUtil.updateEmailAddressVerified( user.getUserId(), true); return user; }

    Se tutto è andato a buon fine ci trovermo autenticati con il nostro account social su liferay

     

  5. Per prima cosa scarichiamo e installiamo Apache Directory Studio.


    Configuriazmo la connessione al nostro LDAP e apriamo la vista schema browser.

    Ho avuto la necessità di aggiungere degli attributi all'object class person (contenuto nello schema core)
     

    Tramite l'apposita funzionalità  di Apache Directory Studio aggiungiamo un attributo di cui dovremo specificare

    -OID( Objecyt Identifier) deve essere univoco

    -Alias(un name per riconoscere il nostro attributo)

    -Descrizione

    mentre allo step due dovremmo inserire

    -Sintassi(per le stringhe selezioniamo Directory String)

    -Tipo di valore (single value, multi value etc)

    Ora dobbiamo collegarlo all'object class person.

    Dall'elenco di tutti gli object class selezioniamo Person ( o un object class differente) e aggiungiamo l'attributo che abbiamo appena creato tra i mandatory attributes o tra gli optional attributes.

    Ora siamo pronti per esportare ed importare le modifiche nel nostro ldap. Ogni modifica allo schema richiederà un riavvio di apacheDS.

    Se l'operazione di import non dovesse andare a buon fine (come è succcesso a me), dovremo modificare il file ldif manualmente.

    Editiamo il file ldif dello schema core con un qualsiasi editor di testo ( o da linea di comando, se preferite) gli attributi e la configurazione dell'object class recuperati dal file LDIF esportato in precedenza.

    Salviamo, riavviamo e il gioco è fatto :)