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