/** * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.webui.application.portlet; import java.io.IOException; import java.util.Locale; import java.util.ResourceBundle; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.EventRequest; import javax.portlet.EventResponse; import javax.portlet.PortletConfig; import javax.portlet.PortletContext; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; import org.exoplatform.commons.utils.PortalPrinter; import org.exoplatform.commons.utils.Safe; import org.exoplatform.resolver.ApplicationResourceResolver; import org.exoplatform.resolver.PortletResourceResolver; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.web.application.ApplicationLifecycle; import org.exoplatform.web.application.RequestContext; import org.exoplatform.webui.application.StateManager; import org.exoplatform.webui.application.WebuiApplication; import org.exoplatform.webui.application.WebuiRequestContext; import org.exoplatform.webui.core.UIApplication; import org.exoplatform.webui.core.UIComponent; import org.exoplatform.webui.core.UIPortletApplication; import org.exoplatform.webui.event.Event; import org.exoplatform.webui.event.Event.Phase; /** * May 26, 2006 * * A portlet application. Every call made to a portlet deployed in eXo PC - and using eXo web framework - is going through this * class */ public class PortletApplication extends WebuiApplication { protected static Log log = ExoLogger.getLogger(PortletApplication.class); public static final String PORTLET_EVENT_VALUE = "portletEventValue"; /** * The configuration parameter of this portlet */ private PortletConfig portletConfig_; /** * The id of this portlet */ private String applicationId_; /** * This constructor has 2 purposes: 1) recreate the application id 2) configure the resource resolver to look in different * UIR scheme (here app: and par:) */ public PortletApplication(PortletConfig config) { portletConfig_ = config; PortletContext pcontext = config.getPortletContext(); String contextName = pcontext.getPortletContextName(); applicationId_ = contextName + "/" + config.getPortletName(); ApplicationResourceResolver resolver = new ApplicationResourceResolver(); resolver.addResourceResolver(new PortletResourceResolver(pcontext, "app:")); resolver.addResourceResolver(new PortletResourceResolver(pcontext, "par:")); setResourceResolver(resolver); } public String getApplicationId() { return applicationId_; } public String getApplicationName() { return portletConfig_.getPortletName(); } public String getApplicationGroup() { return portletConfig_.getPortletContext().getPortletContextName(); } @Override public String getApplicationType() { return JSR168_APPLICATION_TYPE; } public ResourceBundle getResourceBundle(Locale locale) { return portletConfig_.getResourceBundle(locale); } @SuppressWarnings("unused") public ResourceBundle getOwnerResourceBundle(String username, Locale locale) { return null; } public String getApplicationInitParam(String name) { return portletConfig_.getInitParameter(name); } /** * The processAction() method is the one modelled according to the Portlet API specification * * The process is quite simple and here are te different steps done in the method: * * 1) The current instance of the WebuiRequestContext (stored in a ThreadLocal in the class) is referenced 2) A new request * context of type PortletRequestContext (which extends the class WebuiRequestContext) is created as a child of the current * context instance 3) The new context is place inside the ThreadLocal and hence overides its parent one there, only for the * portlet request lifeciclye 4) The method onStartRequest() is called in all the ApplicationLifecycle objects referenced in * the webui configuration XML file 5) The StateManager object (in case of portlet it is an object of type * ParentAppStateManager) is used to get the RootComponent also referenced in the XML configuration file 6) The methods * processDecode(UIApplication, WebuiRequestContext) and processAction(UIApplication, WebuiRequestContext) are then called * 7) Finally, a flag, to tell that the processAction phase was done, in the context is set to true and the parent context * is restored in the Threadlocal */ public void processAction(ActionRequest req, ActionResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance(); PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext); WebuiRequestContext.setCurrentInstance(context); try { for (ApplicationLifecycle<RequestContext> lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context); } StateManager sm = getStateManager(); UIApplication uiApp = sm.restoreUIRootComponent(context); context.setUIApplication(uiApp); uiApp.processDecode(context); if (!context.isResponseComplete() && !context.getProcessRender()) { uiApp.processAction(context); } // Store ui root sm.storeUIRootComponent(context); } finally { context.setAppLifecycleStarted(true); WebuiRequestContext.setCurrentInstance(parentAppRequestContext); } } /** * This method is called when a JSR 286 event is targeting the current portlet. * * The event is transformed in a WebUI event using the convention that the 286 event name will target the * EventNameActionListener class of one of the UIComponent in the portlet uicomponent tree * * The event value is passed as an attribute of the PortletRequestContext */ public void processEvent(EventRequest req, EventResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance(); PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext); WebuiRequestContext.setCurrentInstance(context); try { for (ApplicationLifecycle<RequestContext> lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context); } StateManager sm = getStateManager(); UIApplication uiApp = sm.restoreUIRootComponent(context); context.setUIApplication(uiApp); javax.portlet.Event portletEvent = req.getEvent(); context.setAttribute(PORTLET_EVENT_VALUE, portletEvent.getValue()); Event<UIComponent> uiEvent = uiApp.createEvent(portletEvent.getName(), Phase.PROCESS, context); uiEvent.broadcast(); // Store ui root sm.storeUIRootComponent(context); } finally { context.setAppLifecycleStarted(true); WebuiRequestContext.setCurrentInstance(parentAppRequestContext); } } /** * This method is called when a JSR 286 serveResource lifecycle method is targeting the current portlet. * * 1) The current instance of the WebuiRequestContext (stored in a ThreadLocal in the class) is referenced 2) A new request * context of type PortletRequestContext (which extends the class WebuiRequestContext) is created as a child of the current * context instance 3) The new context is place inside the ThreadLocal and hence overides its parent one there, only for the * portlet request lifecycle 4) The method onStartRequest() is called in all the ApplicationLifecycle objects referenced in * the webui configuration XML file 5) The StateManager object (in case of portlet it is an object of type * ParentAppStateManager) is used to get the RootComponent also referenced in the XML configuration file 6) The method * serveResource of UIPortletApplication is called 7) Finally, the method onEndRequest() is called on every * ApplicationLifecycle referenced in the portlet configuration XML file and the parent WebuiRequestContext is restored */ public void serveResource(ResourceRequest req, ResourceResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance(); PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext); WebuiRequestContext.setCurrentInstance(context); try { for (ApplicationLifecycle<RequestContext> lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context); } StateManager sm = getStateManager(); UIApplication uiApp = sm.restoreUIRootComponent(context); context.setUIApplication(uiApp); if (uiApp instanceof UIPortletApplication) { ((UIPortletApplication) uiApp).serveResource(context); } // Store ui root sm.storeUIRootComponent(context); } finally { try { for (ApplicationLifecycle<RequestContext> lifecycle : getApplicationLifecycle()) { lifecycle.onEndRequest(this, context); } } catch (Exception exception) { log.error("Error while trying to call onEndRequest of the portlet ApplicationLifecycle", exception); } WebuiRequestContext.setCurrentInstance(parentAppRequestContext); } } /** * The render method business logic is quite similar to the processAction() one. * * 1) A PortletRequestContext object is created (or extracted from the cache if it already exists) and initialized 2) The * PortletRequestContext replaces the parent one in the WebuiRequestContext ThreadLocal object 3) If the portal has already * called the portlet processAction() then the call to all onStartRequest of the ApplicationLifecycle has already been made, * otherwise we call them 4) The ParentStateManager is also used to get the UIApplication, as we have seen it delegates the * call to the PortalStateManager which caches the UI component root associated with the current application 5) the * processRender() method of the UIPortletApplucaton is called 6) Finally, the method onEndRequest() is called on every * ApplicationLifecycle referenced in the portlet configuration XML file and the parent WebuiRequestContext is restored * */ public void render(RenderRequest req, RenderResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance(); PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext); WebuiRequestContext.setCurrentInstance(context); try { if (!context.isAppLifecycleStarted()) { for (ApplicationLifecycle<RequestContext> lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context); } } StateManager sm = getStateManager(); UIApplication uiApp = sm.restoreUIRootComponent(context); context.setUIApplication(uiApp); if (!context.isResponseComplete()) { UIPortletApplication uiPortletApp = (UIPortletApplication) uiApp; uiPortletApp.processRender(this, context); } uiApp.setLastAccessApplication(System.currentTimeMillis()); // Store ui root sm.storeUIRootComponent(context); } finally { // Close the writer Safe.close(context.getWriter()); // try { for (ApplicationLifecycle<RequestContext> lifecycle : getApplicationLifecycle()) { lifecycle.onEndRequest(this, context); } } catch (Exception exception) { log.error("Error while trying to call onEndRequest of the portlet ApplicationLifecycle", exception); } WebuiRequestContext.setCurrentInstance(parentAppRequestContext); } } /** * In this method we try to get the PortletRequestContext object from the attribute map of the parent WebuiRequestContext. * * If it is not cached then we create a new instance, if it is cached then we init it with the correct writer, request and * response objects * * We finally cache it in the parent attribute map * */ private PortletRequestContext createRequestContext(PortletRequest req, PortletResponse res, WebuiRequestContext parentAppRequestContext) throws IOException { String attributeName = getApplicationId() + "$PortletRequest"; PortletRequestContext context = (PortletRequestContext) parentAppRequestContext.getAttribute(attributeName); PortalPrinter w = null; if (res instanceof RenderResponse) { RenderResponse renderRes = (RenderResponse) res; renderRes.setContentType("text/html; charset=UTF-8"); w = new PortalPrinter(renderRes.getPortletOutputStream(), true, 0); } if (context != null) { context.init(w, req, res); } else { context = new PortletRequestContext(parentAppRequestContext, this, w, req, res); parentAppRequestContext.setAttribute(attributeName, context); } return context; } }