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