/* * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library 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. * * Contributors: * Thierry Delprat */ package org.nuxeo.ecm.webapp.seam; import static org.jboss.seam.ScopeType.EVENT; import static org.jboss.seam.annotations.Install.FRAMEWORK; import java.io.Serializable; import java.security.Principal; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.core.Events; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants; import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; import org.nuxeo.ecm.platform.ui.web.util.BaseURL; import org.nuxeo.ecm.webapp.helpers.EventNames; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.reload.ReloadService; import org.nuxeo.runtime.service.TimestampedService; /** * Simple Seam bean to control the Reload Action * * @author tiry */ @Name("seamReload") @Scope(EVENT) @Install(precedence = FRAMEWORK) public class NuxeoSeamHotReloader implements Serializable { private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(NuxeoSeamHotReloader.class); @In(required = false, create = true) private transient Principal currentUser; /** * Returns true if dev mode is set * * @since 5.6 * @see Framework#isDevModeSet() */ @Factory(value = "nxDevModeSet", scope = ScopeType.EVENT) public boolean isDevModeSet() { return Framework.isDevModeSet(); } @Factory(value = "seamHotReloadIsEnabled", scope = ScopeType.APPLICATION) public boolean isHotReloadEnabled() { return SeamHotReloadHelper.isHotReloadEnabled(); } /** * Returns true if dev mode is set and current user is an administrator. * * @since 5.6 * @return */ public boolean getCanTriggerFlush() { NuxeoPrincipal pal = null; if (currentUser instanceof NuxeoPrincipal) { pal = (NuxeoPrincipal) currentUser; } return isDevModeSet() && pal != null && pal.isAdministrator(); } /** * Calls the {@link ReloadService#flush()} method, that should trigger the * reset of a bunch of caches shared by all users, and sends a Seam event * to propagate this to other Seam components. * <p> * Does nothing if not in dev mode. * <p> * The reload service flush method should already be triggerd by * install/uninstall of modules. This method makes it possible to force it * again, and to propagate it to the Seam layer for current user. * * @see #resetSeamComponentsCaches() * @see #shouldResetCache(Long) * @see #shouldResetCache(TimestampedService, Long) * @since 5.6 */ public String doFlush() { if (Framework.isDevModeSet()) { FacesContext faces = FacesContext.getCurrentInstance(); String viewId = faces.getViewRoot().getViewId(); URLPolicyService service = Framework.getLocalService(URLPolicyService.class); String outcome = service.getOutcomeFromViewId(viewId, null); ReloadService srv = Framework.getLocalService(ReloadService.class); try { srv.flush(); } catch (Exception e) { log.error("Error while flushing the application in dev mode", e); } Events.instance().raiseEvent(EventNames.FLUSH_EVENT); // return the current view id otherwise an error appears in logs // because navigation cache needs to be rebuilt after execution return outcome; } return null; } /** * Returns true if reload service has sent a runtime flush event since * given timestamp. * * @since 5.6 * @param cacheTimestamp * @see ReloadService#lastFlushed() */ public boolean shouldResetCache(Long cacheTimestamp) { try { ReloadService service = Framework.getService(ReloadService.class); if (cacheTimestamp == null || service == null) { return true; } Long serviceTimestamp = service.lastFlushed(); if (serviceTimestamp == null) { return false; } if (cacheTimestamp.compareTo(serviceTimestamp) < 0) { return true; } return false; } catch (Exception e) { log.error(e, e); return true; } } /** * Returns the last flush timestamp held by the {@link ReloadService}. * * @since 5.6 * @see ReloadService * @see TimestampedService */ public Long getCurrentCacheTimestamp() { Long res = null; try { ReloadService service = Framework.getService(ReloadService.class); if (service != null) { res = service.lastFlushed(); } } catch (Exception e) { log.error(e, e); } return res; } /** * Returns true if given service has changed since given timestamp. * * @since 5.6 * @param service * @param cacheTimestamp * @see TimestampedService */ public boolean shouldResetCache(TimestampedService service, Long cacheTimestamp) { if (cacheTimestamp == null || service == null) { return true; } Long serviceTimestamp = service.getLastModified(); if (serviceTimestamp == null) { return false; } if (cacheTimestamp.compareTo(serviceTimestamp) < 0) { return true; } return false; } /** * Resets most caches of the Seam application. * <p> * This is useful when a change is detected on the reload service. * <p> * For compatibility and easier upgrade, this method listens to the * {@link EventNames#FLUSH_EVENT}, and sends other events to the Seam layer * for other components to reset their own cache without needing to change * their code. * <p> * In the future, this behaviour could be removed, so Seam component should * reset their cache listening to the {@link EventNames#FLUSH_EVENT} * directly. * * @since 5.6 */ @Observer(value = { EventNames.FLUSH_EVENT }, create = false) @BypassInterceptors public void triggerResetOnSeamComponents() { String[] events = { EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED, EventNames.LOCATION_SELECTION_CHANGED, EventNames.CONTENT_ROOT_SELECTION_CHANGED, EventNames.DOMAIN_SELECTION_CHANGED, EventNames.LOCAL_CONFIGURATION_CHANGED, }; Events seamEvents = Events.instance(); for (String event : events) { seamEvents.raiseEvent(event); } } /** * Triggers a full reload of Seam context and components. * <p> * Needs the Seam debug jar to be present and Seam debug mode to be * enabled. */ public String doReload() { final FacesContext facesContext = FacesContext.getCurrentInstance(); if (facesContext == null) { return null; } HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse(); HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest(); String url = BaseURL.getBaseURL(request); url += "restAPI/seamReload"; try { response.resetBuffer(); response.sendRedirect(url); response.flushBuffer(); request.setAttribute(NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE); facesContext.responseComplete(); } catch (Exception e) { log.error("Error during redirect", e); } return null; } }