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}