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}