001/* 002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Thierry Delprat 016 */ 017package org.nuxeo.ecm.webapp.seam; 018 019import static org.jboss.seam.ScopeType.EVENT; 020import static org.jboss.seam.annotations.Install.FRAMEWORK; 021 022import java.io.IOException; 023import java.io.Serializable; 024import java.security.Principal; 025 026import javax.faces.context.FacesContext; 027import javax.servlet.http.HttpServletRequest; 028import javax.servlet.http.HttpServletResponse; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.jboss.seam.ScopeType; 033import org.jboss.seam.annotations.Factory; 034import org.jboss.seam.annotations.In; 035import org.jboss.seam.annotations.Install; 036import org.jboss.seam.annotations.Name; 037import org.jboss.seam.annotations.Observer; 038import org.jboss.seam.annotations.Scope; 039import org.jboss.seam.annotations.intercept.BypassInterceptors; 040import org.jboss.seam.core.Events; 041import org.nuxeo.ecm.core.api.NuxeoPrincipal; 042import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants; 043import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; 044import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 045import org.nuxeo.ecm.webapp.helpers.EventNames; 046import org.nuxeo.runtime.api.Framework; 047import org.nuxeo.runtime.reload.ReloadService; 048import org.nuxeo.runtime.service.TimestampedService; 049 050/** 051 * Simple Seam bean to control the Reload Action 052 * 053 * @author tiry 054 */ 055@Name("seamReload") 056@Scope(EVENT) 057@Install(precedence = FRAMEWORK) 058public class NuxeoSeamHotReloader implements Serializable { 059 060 private static final long serialVersionUID = 1L; 061 062 private static final Log log = LogFactory.getLog(NuxeoSeamHotReloader.class); 063 064 @In(required = false, create = true) 065 private transient Principal currentUser; 066 067 /** 068 * Returns true if dev mode is set 069 * 070 * @since 5.6 071 * @see Framework#isDevModeSet() 072 */ 073 @Factory(value = "nxDevModeSet", scope = ScopeType.EVENT) 074 public boolean isDevModeSet() { 075 return Framework.isDevModeSet(); 076 } 077 078 @Factory(value = "seamHotReloadIsEnabled", scope = ScopeType.APPLICATION) 079 public boolean isHotReloadEnabled() { 080 return SeamHotReloadHelper.isHotReloadEnabled(); 081 } 082 083 /** 084 * Returns true if dev mode is set and current user is an administrator. 085 * 086 * @since 5.6 087 * @return 088 */ 089 public boolean getCanTriggerFlush() { 090 NuxeoPrincipal pal = null; 091 if (currentUser instanceof NuxeoPrincipal) { 092 pal = (NuxeoPrincipal) currentUser; 093 } 094 return isDevModeSet() && pal != null && pal.isAdministrator(); 095 } 096 097 /** 098 * Calls the {@link ReloadService#flush()} method, that should trigger the reset of a bunch of caches shared by all 099 * users, and sends a Seam event to propagate this to other Seam components. 100 * <p> 101 * Does nothing if not in dev mode. 102 * <p> 103 * The reload service flush method should already be triggerd by install/uninstall of modules. This method makes it 104 * possible to force it again, and to propagate it to the Seam layer for current user. 105 * 106 * @see #resetSeamComponentsCaches() 107 * @see #shouldResetCache(Long) 108 * @see #shouldResetCache(TimestampedService, Long) 109 * @since 5.6 110 */ 111 public String doFlush() { 112 if (Framework.isDevModeSet()) { 113 FacesContext faces = FacesContext.getCurrentInstance(); 114 String viewId = faces.getViewRoot().getViewId(); 115 URLPolicyService service = Framework.getLocalService(URLPolicyService.class); 116 String outcome = service.getOutcomeFromViewId(viewId, null); 117 ReloadService srv = Framework.getLocalService(ReloadService.class); 118 srv.flush(); 119 Events.instance().raiseEvent(EventNames.FLUSH_EVENT); 120 // return the current view id otherwise an error appears in logs 121 // because navigation cache needs to be rebuilt after execution 122 return outcome; 123 } 124 return null; 125 } 126 127 /** 128 * Returns true if reload service has sent a runtime flush event since given timestamp. 129 * 130 * @since 5.6 131 * @param cacheTimestamp 132 * @see ReloadService#lastFlushed() 133 */ 134 public boolean shouldResetCache(Long cacheTimestamp) { 135 if (cacheTimestamp == null) { 136 return true; 137 } 138 Long serviceTimestamp = getCurrentCacheTimestamp(); 139 if (serviceTimestamp == null) { 140 return false; 141 } 142 if (cacheTimestamp.compareTo(serviceTimestamp) < 0) { 143 return true; 144 } 145 return false; 146 } 147 148 /** 149 * Returns the last flush timestamp held by the {@link ReloadService}. 150 * 151 * @since 5.6 152 * @see ReloadService 153 * @see TimestampedService 154 */ 155 public Long getCurrentCacheTimestamp() { 156 ReloadService service = Framework.getService(ReloadService.class); 157 return service.lastFlushed(); 158 } 159 160 /** 161 * Returns true if given service has changed since given timestamp. 162 * 163 * @since 5.6 164 * @param service 165 * @param cacheTimestamp 166 * @see TimestampedService 167 */ 168 public boolean shouldResetCache(TimestampedService service, Long cacheTimestamp) { 169 if (cacheTimestamp == null || service == null) { 170 return true; 171 } 172 Long serviceTimestamp = service.getLastModified(); 173 if (serviceTimestamp == null) { 174 return false; 175 } 176 if (cacheTimestamp.compareTo(serviceTimestamp) < 0) { 177 return true; 178 } 179 return false; 180 } 181 182 /** 183 * Resets most caches of the Seam application. 184 * <p> 185 * This is useful when a change is detected on the reload service. 186 * <p> 187 * For compatibility and easier upgrade, this method listens to the {@link EventNames#FLUSH_EVENT}, and sends other 188 * events to the Seam layer for other components to reset their own cache without needing to change their code. 189 * <p> 190 * In the future, this behaviour could be removed, so Seam component should reset their cache listening to the 191 * {@link EventNames#FLUSH_EVENT} directly. 192 * 193 * @since 5.6 194 */ 195 @Observer(value = { EventNames.FLUSH_EVENT }, create = false) 196 @BypassInterceptors 197 public void triggerResetOnSeamComponents() { 198 String[] events = { EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED, 199 EventNames.LOCATION_SELECTION_CHANGED, EventNames.CONTENT_ROOT_SELECTION_CHANGED, 200 EventNames.DOMAIN_SELECTION_CHANGED, EventNames.LOCAL_CONFIGURATION_CHANGED, }; 201 Events seamEvents = Events.instance(); 202 for (String event : events) { 203 seamEvents.raiseEvent(event); 204 } 205 } 206 207 /** 208 * Triggers a full reload of Seam context and components. 209 * <p> 210 * Needs the Seam debug jar to be present and Seam debug mode to be enabled. 211 */ 212 public String doReload() { 213 final FacesContext facesContext = FacesContext.getCurrentInstance(); 214 if (facesContext == null) { 215 return null; 216 } 217 218 HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse(); 219 HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest(); 220 221 String url = BaseURL.getBaseURL(request); 222 url += "restAPI/seamReload"; 223 224 try { 225 response.resetBuffer(); 226 response.sendRedirect(url); 227 response.flushBuffer(); 228 request.setAttribute(NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE); 229 facesContext.responseComplete(); 230 } catch (IOException e) { 231 log.error("Error during redirect", e); 232 } 233 return null; 234 } 235 236}