Portal e Portlet ClassLoader Mancini Marco Project Manager, 3F Consulting s.r.l.
Classloading in liferay Tre differenti classpath: Global classpath, tutte le librerie presenti nella nostra JVM, e nelle lib e lib/ext del nostro tomcat. Portal classpath, tutte le librerie di liferay, nel caso ti tomcat tette le librerie dell applicazione ROOT posizionate nella cartella lib. Plugin classpath, tutte le librerie dei nostri plugin, «solitamente» posizionati nelle rispettive cartelle lib (portlet, hook, web).
Classloading in liferay
Classloading in liferay Svantaggi : Non poter accedere a tutte librerie del portale dai plugin; Il portale non può accedere alle librerie dei nostri plugin; Presenta di molti classloader; Vantaggi: Indipendenza delle librerie; Disaccoppiamento tra portale e plugin; Riduzione dei conflitti tra librerie
Classloading in liferay Alcune note: «Jsp» Hook plugin, utilizzano il classpath del portale perché sostituiscono le jsp originali del portale che vengono rinominati «nomejsp.portal.jsp». «liferay-plugin-package.properties» package.properties in questo file presente in ogni plugin per liferay è possibile specificare librerie presenti nell applicazione ROOT di tomcat. Queste librerie saranno caricate nel classpath del plugin senza essere presenti nella cartella lib del nostro progetto. Il war non conterrà le librerie ma sarà il portale a copiarle dentro il nostro plugin. «portal-impl.jar» questo sistema rende impossibile l accesso a tutte le classi che si trovano in questa libreria che non deve MAI essere inclusa nei nostri plugin.
Portal e Portlet Class Loader Util Classi di utilità : com.liferay.portal.kernel.util.portalclassloaderutil, per accedere al class loader del portale; com.liferay.portal.kernel.portlet.portletclassloaderutil, per accedere al class loader dei singoli portlet/plugin; com.liferay.portal.kernel.util.methodkey, l l til th classe che fornisce metodi per recuperare la rappresentazione serializzata di un metodo; com.liferay.portal.kernel.util.portletclassinvoker, classe che permette di invocare i «MethodKey» parametrizzati, su uno specifico portlet/plugin; com.liferay.portal.kernel.util PortalClassInvoker, classe che permette di invocare «MethodKey» direttamente con il class loader del portale; com.liferay.portal.kernel.util.classresolverutil, classe che mette a disposizione delle utilità per recuperare la «Class» di nostro interesse a partire dal classloader e dal suo nome.
Portal Class Loader Come utilizzare il Portal class loader di un plugin: sample-sign-in-portlet p Come il portale utilizza a sua volta il portal class loader per offrire funzionalità strutturate agli sviluppatori: Actionable dynamic query
Portal Class Loader Liferay sample-sign-in-portlet
Portal Class Loader: sample-sign-in-portlet Il sorgente è accessibile al seguente link https://github.com/liferay/liferay-plugins/tree/master/portlets/sample-sign-in-portlet Portlet che è utilizzato per effettuare il login sul portale; Portlet che utilizza il Portal Class Loader per accedere a classi che si trovano nel portal-impl.jar; IMPORTANTE Mai includere la libreria portal-impl.jar all interno di un custom plugin; I metodi accessibili dai plugin sono esposti dalla libreria portalservice.jar; Il portal-impl non è realizzato per essere incluso nei custom plugin e contiene tutte le classi core del portale.
Portal Class Loader: sample-sign-in-portlet All interno della jsp /docroot/view.jsp alla (riga 65): <% MethodKey methodkey = new MethodKey(ClassResolverUtil.resolveByPortalClassLoader( "com.liferay.portlet.login.util.loginutil"), "getlogin", HttpServletRequest.class, String.class, Company.class); String login = GetterUtil.getString((String)PortalClassInvoker.invoke(false, methodkey, request, "login", company)); boolean rememberme = ParamUtil.getBoolean(request, "rememberme"); %> La stringa login utilizzata per precaricare l input (riga 92): <aui:input name="login" style="width: 120px;" type="text" value="<%= HtmlUtil.escape(login) %>" />
Portal Class Loader: sample-sign-in-portlet All interno della jsp /docroot/view.jsp alla (riga 65): <% MethodKey methodkey = new MethodKey(ClassResolverUtil.resolveByPortalClassLoader( "com.liferay.portlet.login.util.loginutil"), "getlogin", HttpServletRequest.class, String.class, Company.class); String login = GetterUtil.getString((String)PortalClassInvoker.invoke(false, methodkey, request, "login", company)); boolean rememberme = ParamUtil.getBoolean(request, "rememberme"); %> La stringa login utilizzata per precaricare l input (riga 92): <aui:input name="login" style="width: 120px;" type="text" value="<%= HtmlUtil.escape(login) %>" />
Portal Class Loader: sample-sign-in-portlet All interno della jsp /docroot/view.jsp alla (riga 65): <% MethodKey methodkey = new MethodKey(ClassResolverUtil.resolveByPortalClassLoader( "com.liferay.portlet.login.util.loginutil"), "getlogin", HttpServletRequest.class, String.class, Company.class); String login = GetterUtil.getString((String)PortalClassInvoker.invoke(false, methodkey, request, "login", company)); boolean rememberme = ParamUtil.getBoolean(request, "rememberme"); %> La stringa login utilizzata per precaricare l input (riga 92): <aui:input name="login" style="width: 120px;" type="text" value="<%= HtmlUtil.escape(login) %>" />
Portal Class Loader: sample-sign-in-portlet Metodo utilizzato della classe ClassResolverUtil.java (riga 56) public class ClassResolverUtil { public static ti Class<?>? resolvebyportalclassloader(string lcl i classname) { } ClassLoader portalclassloader =PortalClassLoaderUtil.getClassLoader(); try { return Class.forName(className, false, portalclassloader); }catch (ClassNotFoundException cnfe) { } throw new RuntimeException(cnfe);
Portal Class Loader: sample-sign-in-portlet com.liferay.samplesignin.portlet.signinportlet (riga 43) String classname = "com.liferay.portlet.login.action.loginaction"; PortletConfig portletconfig = getportletconfig(); NoRedirectActionResponse noredirectactionresponse = new NoRedirectActionResponse(actionResponse); try { PortletActionInvoker.processAction( classname, portletconfig, actionrequest, noredirectactionresponse); } catch (Exception e) { _log.error(e, e); } PortletActionInvoker, classe di utilità per invocare «Action» di struts del portale da un plugin custom.
Portal Class Loader: sample-sign-in-portlet Il plugin invoca una action che si trova nel class loader del portale. public class PortletActionInvoker { public static ti void processaction( String classname, PortletConfig tc portletconfig, tc } ActionRequest actionrequest, ActionResponse actionresponse) throws Exception { } MethodKey methodkey = new MethodKey( ClassResolverUtil.resolveByPortalClassLoader(className), "processaction", new Class<?>[] { ClassResolverUtil.resolveByPortalClassLoader( "org.apache.struts.action.actionmapping"), ClassResolverUtil.resolveByPortalClassLoader( "org.apache.struts.action.actionform"), PortletConfig.class, ActionRequest.class, class ActionResponse.classclass }); PortalClassInvoker.invoke(true, methodkey, null, null, portletconfig, actionrequest, actionresponse);
Portal Class Loader Liferay Actionable Dynamic Query
Portal Class Loader: Actionable dynamic query Presenti dalla versione 6.2 del portale; Utilizzati dal portale all interno delle classi per l indicizzazione dei contenuti; Automaticamente generate dal service builder per le entità custom; Utilizzabile nei plugin custom per realizzare custom query: o o o o Su una specifica entità; Ottimizzate per un numero elevato di oggetti; Possibilità di specificare «condizioni» aggiuntive; Possibilità di eseguire operazioni per ogni riga ritornata dalla query. Si trovano nei service dei plugin custom e quindi possono essere utilizzate da altri plugin;
Portal Class Loader: Actionable dynamic query Actionable Dynamic Query di portale: Blog public abstract class BlogsEntryActionableDynamicQuery extends BaseActionableDynamicQuery { public BlogsEntryActionableDynamicQuery() throws SystemException { } setbaselocalservice(blogsentrylocalserviceutil.getservice()); setclass(blogsentry.class); setclassloader(portalclassloaderutil.getclassloader()); setprimarykeypropertyname("entryid"); Actionable Dynamic Query di plugin: public abstract class HDProductActionableDynamicQuery extends BaseActionableDynamicQuery{ public HDProductActionableDynamicQuery() throws SystemException { setbaselocalservice(hdproductlocalserviceutil.getservice()); setclass(hdproduct.class); } setclassloader( it.smc.liferay.helpdesk.service.clpserializer.class.getclassloader()); setprimarykeypropertyname("productid");
Portal Class Loader: Actionable dynamic query Metodo che utilizza la logica del class loader per generalizzare l utilizzo delle actionable dynamic query quindi utilizzabili da tutti i plugin. com.liferay.portal.kernel.dao.orm.baseactionabledynamicquery l l ti i (riga 49) public void performactions(long startprimarykey, long endprimarykey) throws PortalException, ti SystemException ti { DynamicQuery dynamicquery = DynamicQueryFactoryUtil.forClass( _clazz, _classloader); N.B. Il class loader può essere utilizzato per eseguire Dynamic Query anche su plugin esterni senza dover includere nessuna libreria all interno dei nostri plugin
Portal Class Loader: Actionable dynamic query Dove utilizzarle all interno dell indexer : protected void doreindex(string[] ids) throws Exception { } long companyid = GetterUtil.getLong(ids[0]); reindexhdproducts(companyid); protected t void reindexhdproducts(long d companyid) throws Exception { }
Portal Class Loader: Actionable dynamic query Come utilizzarle l all interno del nostro indexer: ActionableDynamicQuery y actionabledynamicquery y = new HDProductActionableDynamicQuery() { @Override protected void addcriteria(dynamicquery dynamicquery) { Property property p = PropertyFactoryUtil.forName("status"); y ( ) dynamicquery.add(property.eq(workflowconstants.status_approved)); } @Override protected void performaction(object object) throws PortalException { HDProduct hdproduct = (HDProduct)object; Document document = getdocument(hdproduct); adddocument(document); } }; actionabledynamicquery.setcompanyid(companyid); actionabledynamicquery.setsearchengineid(getsearchengineid()); actionabledynamicquery.performactions();
Portlet Class Loader Portlet Class Loader
Portlet Class Loader Possiamo accedere a classi e funzionalità residenti in altri plugin senza dover includere nessuna libreria o creare dipendenze tra plugin; Perché utilizzare il portlet class loader? Condividere funzionalità tra plugin senza creare dipendenza riducendo l accoppiamento. Risolvere il problema della dipendenza bidirezionale tra plugin. (alternativa al messagebus)
Portlet Class Loader: Servlet Context e tag lib Un Servlet Context è costituito da un gruppo di Servlet, pagine JSP o altre pagine web che condividono tra di loro risorse e dati. Il tag da utilizzare è «liferay-util:include» <%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util" %> <liferay-util:include page='/html/plugin_page.jsp' servletcontext="plugin PLUGIN_SERVLET_CONTEXT CONTEXT" > </liferay-util:include> PLUGIN_SERVLET_CONTEXT è il nome del plugin (per il nostro helpdesk è help_desk-portlet) In alternativa è possibile specificare il portlet id (questo solo per i plugin di tipo portlet): <%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util" %> <liferay-util:include page='/html/plugin_page.jsp' portletid="plugin_ PORTLET_ ID" > </liferay-util:include>
Portlet Class Loader Liferay chat-portlet
Portlet Class Loader: chat-portlet Il sorgente è accessibile al seguente link: https://github.com/liferay/liferay-plugins/tree/master/portlets/chat-portlet Portlet utilizzato dal portale per gestire una chat tra gli utenti; Portlet che permette ad altri plugin di estenere le sue funzionalità utilizzando la logica del «Servlet Context»;
Portlet Class Loader: chat-portlet Il portlet ha una sua classe di utilità per registrare o deregistrare un plugin di estensione. package com.liferay.chat.util; public class ChatExtensionsUtil { public static void register(string servletcontextname, String path) { } public static void unregister(string servletcontextname) { }
Portlet Class Loader: chat-portlet All interno della «view.jsp» (riga 161) vengono caricate le jsp dei plugin registrati. <div class="chat-extensions hide"> <% %> <% %> </div> Map<String, String> extensions = ChatExtensionsUtil.getExtensions(); Set<String> servletcontextnames = extensions.keyset(); for (String servletcontextname : servletcontextnames) { String extensionpath = extensions.get(servletcontextname); ServletContext extensionservletcontext = ServletContextPool.get(servletContextName); t tp l t( l t tn <liferay-util:include page="<%= extensionpath %>" servletcontext="<%= extensionservletcontext %>" /> }
Portlet Class Loader help_desk_chat-hook
Portlet Class Loader: help_desk_chat-hook Plugin che utilizza il sistema di estensione della chat; Questo plugin a sua volta includerà una jsp presente in un terzo plugin «help_desk-portlet»; All interno del pannello della chat sarà visualizzato un nuovo pulsante con il numero dei clienti e dei fornitori. Al «click» del pulsante verrà aperto un popup con la portlet di help_desk.
Portlet Class Loader: help_desk_chat-hook
Portlet Class Loader: help_desk_chat-hook
Portlet Class Loader: help_desk_chat-hook
Portlet Class Loader: help_desk_chat-hook
Portlet Class Loader: help_desk_chat-hook Per registrarsi al plugin della chat è stata realizzata la seguente «startup action»: public class StartUpChat extends SimpleAction { public void run(string[] ids) throws ActionException { ClassLoader classloader = PortletClassLoaderUtil.getClassLoader("1_WAR_chatportlet"); MethodKey _registermethodkey; try { _registermethodkey = new MethodKey( } ClassResolverUtil.resolve("com.liferay.chat.util.ChatExtensionsUtil", l l lif h t Ch te t i Util" classloader), "register", String.class, String.class); PortletClassInvoker.invoke(false, "1_WAR_chatportlet", _registermethodkey, "help_ desk_ chat-hook", "/html/view.jsp"); catch (Exception e) {
Portlet Class Loader: help_desk_chat-hook Per registrarsi al plugin della chat è stata realizzata la seguente «startup action»: public class StartUpChat extends SimpleAction { public void run(string[] ids) throws ActionException { ClassLoader classloader = PortletClassLoaderUtil.getClassLoader("1_WAR_chatportlet"); MethodKey _registermethodkey; try { _registermethodkey = new MethodKey( } ClassResolverUtil.resolve("com.liferay.chat.util.ChatExtensionsUtil", l l lif h t Ch te t i Util" classloader), "register", String.class, String.class); PortletClassInvoker.invoke(false, "1_WAR_chatportlet", _registermethodkey, "help_ desk_ chat-hook", "/html/view.jsp"); catch (Exception e) {
Portlet Class Loader: help_desk_chat-hook Per registrarsi al plugin della chat è stata realizzata la seguente «startup action»: public class StartUpChat extends SimpleAction { public void run(string[] ids) throws ActionException { ClassLoader classloader = PortletClassLoaderUtil.getClassLoader("1_WAR_chatportlet"); MethodKey _registermethodkey; try { _registermethodkey = new MethodKey( } ClassResolverUtil.resolve("com.liferay.chat.util.ChatExtensionsUtil", l l lif h t Ch te t i Util" classloader), "register", String.class, String.class); PortletClassInvoker.invoke(false, "1_WAR_chatportlet", _registermethodkey, "help_ desk_ chat-hook", "/html/view.jsp"); catch (Exception e) {
Portlet Class Loader: help_desk_chat-hook Per registrarsi al plugin della chat è stata realizzata la seguente «startup action»: public class StartUpChat extends SimpleAction { public void run(string[] ids) throws ActionException { ClassLoader classloader = PortletClassLoaderUtil.getClassLoader("1_WAR_chatportlet"); MethodKey _registermethodkey; try { _registermethodkey = new MethodKey( } ClassResolverUtil.resolve("com.liferay.chat.util.ChatExtensionsUtil", l l lif h t Ch te t i Util" classloader), "register", String.class, String.class); PortletClassInvoker.invoke(false, "1_WAR_chatportlet", _registermethodkey, "help_ desk_ chat-hook", "/html/view.jsp"); catch (Exception e) {
Portlet Class Loader: help_desk_chat-hook Registrazione della startup action tramite hook su portal.properties. liferay-hook.xml <hook> <portal-properties>portal.properties</portal-properties> </hook> portal.properties application.startup.events=it.smc.liferay.helpdesk.chat.action.startupchat t t t it lif l h t tu t PortletKeys.java package it.smc.liferay.helpdesk.chat.util; public class PortletKeys extends com.liferay.portal.util.portletkeys{ public final static String HELP_DESK_SERVLET_CONTEXT = "help_desk-portlet"; }
Portlet Class Loader: help_desk_chat-hook «view.jsp» <%@page import="com.liferay.portal.kernel.portlet.liferaywindowstate"%> <%@page import="com.liferay.portal.kernel.servlet.servletcontextpool"%> portal <%@page import="it.smc.liferay.helpdesk.chat.util.portletkeys"%> <%@ include file="/html/init.jsp" %> <li id="chat-helpdesk" class="chat-helpdesk" > <div class="panel-trigger" panelid="chat-helpdesk"> <span class="trigger-name"> <liferay-util:include page='/admin/info_bar.jsp' </span> </div> </li> servletcontext="<%=servletcontextpool.get(portletkeys.help_desk_serv LET_CONTEXT) %>" > <liferay-util:param name="chatbar" value="true"></liferay- util:param> </liferay-util:include>
Portlet Class Loader: help_desk_chat-hook «view.jsp» <liferay-portlet:renderurl var="chaturl" portletname="1_war_help_deskportlet" windowstate="<%=liferaywindowstate.pop_up.tostring() %>"> </liferay-portlet:renderurl> t <script> AUI().ready( function(a) { A.one('.chat-tabs').insert(A.one('#chat-helpdesk')); A.one('#chat-helpdesk').on('click', function(event) { Liferay.Util.openWindow({ dialog: { centered: true, modal: true }, id: '<portlet:namespace/>dialoghelpdesk', title: '<liferay-ui:message key="help-desk" />', uri: '<%=chaturl.tostring()%>' t }); }); } ); </script>
Portlet Class Loader: help_desk_chat-hook «info_bar.jsp» <%@page import="com.liferay.portal.kernel.dao.orm.restrictionsfactoryutil"%> <%@page import="com.liferay.portal.kernel.dao.orm.dynamicquery"%> <%@page import="it.smc.liferay.helpdesk.service.hdcustomerlocalserviceutil"%> h l d k i t L ls i <%@ include file="/init.jsp" %> <% long countcustomer = ; long countproducts = ; boolean chatbar = Boolean.parseBoolean(ParamUtil.get(request, "chatbar", "false")); %> <c:if test="<%=!chatbar %>"> <h2><liferay-ui:message key="information" /> </h2> </c:if> <div style='<%=chatbar?"display:inline-block;":""%>'> <liferay-ui:message key="customers" /> <span class="badge badge-info"> <%=countcustomer %> </span> </div> <div style='<%=chatbar?"display:inline-block;":""%>'> <liferay-ui:message key="products" /> <span class="badge badge-info"> <%=countproducts %> </span> </div>
Portlet Class Loader: help_desk_chat-hook
CASO REALE: MAPIT NOW
Mapit now Mapit Now è un «insieme di plugin» per gestire la geolocalizzazione degli asset su mappa (google, osm, bing, ). https://www.liferay.com/it/marketplace/-/mp/application/39869795 «Portlet Class Loader» per la registrazione dei plugin; «Servlet context» per l estensione della mappa tramite jsp contenute in altri at plugin; pug La mappa è realizzata come componente alloy-ui; Utilizzo del framework «open layer»;
Mapit now
Mapit now
Mapit now
Mapit now
GRAZIE Mancini Marco m.mancini@3fconsulting.it @marcomancini _ 3f