001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (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 *     Anahide Tchertchian
017 *     Florent Guillaume
018 */
019
020package org.nuxeo.ecm.platform.ui.web.rest;
021
022import java.io.IOException;
023
024import javax.el.ELContext;
025import javax.el.ELException;
026import javax.el.ExpressionFactory;
027import javax.el.MethodExpression;
028import javax.faces.context.ExternalContext;
029import javax.faces.context.FacesContext;
030import javax.faces.event.PhaseEvent;
031import javax.faces.event.PhaseId;
032import javax.faces.event.PhaseListener;
033import javax.servlet.ServletException;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpServletResponse;
036import javax.transaction.NotSupportedException;
037import javax.transaction.SystemException;
038
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.jboss.seam.transaction.Transaction;
042import org.nuxeo.ecm.core.api.NuxeoException;
043import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService;
044import org.nuxeo.ecm.platform.url.api.DocumentView;
045import org.nuxeo.ecm.platform.web.common.exceptionhandling.NuxeoExceptionHandler;
046import org.nuxeo.ecm.platform.web.common.exceptionhandling.service.ExceptionHandlingService;
047import org.nuxeo.runtime.api.Framework;
048
049/**
050 * Phase listener helper to perform redirection to meaningful urls.
051 */
052public class RestfulPhaseListener implements PhaseListener {
053
054    private static final long serialVersionUID = 1L;
055
056    private static final Log log = LogFactory.getLog(RestfulPhaseListener.class);
057
058    public static final String SEAM_HOTRELOAD_TRIGGER_ACTION = "#{seamReloadContext.triggerReloadIdNeeded()}";
059
060    protected URLPolicyService service;
061
062    protected URLPolicyService getURLPolicyService() {
063        if (service == null) {
064            service = Framework.getService(URLPolicyService.class);
065        }
066        return service;
067    }
068
069    @Override
070    public PhaseId getPhaseId() {
071        return PhaseId.RENDER_RESPONSE;
072    }
073
074    @Override
075    public void beforePhase(PhaseEvent event) {
076        FacesContext context = event.getFacesContext();
077        HttpServletRequest httpRequest = (HttpServletRequest) context.getExternalContext().getRequest();
078        try {
079            URLPolicyService service = getURLPolicyService();
080            if (service.isCandidateForDecoding(httpRequest)) {
081                // Make sure we're in a transaction, and that Seam knows it.
082                // Sometimes, when there is a page action, SeamPhaseListener
083                // commits the transaction in RENDER_RESPONSE before this
084                // phase listener executes, but does not start a new one.
085                // (SeamPhaseListener.handleTransactionsAfterPageActions)
086                if (!Transaction.instance().isActiveOrMarkedRollback()) {
087                    Transaction.instance().begin();
088                }
089                // hot reload hook, maybe to move up so that it's handled on
090                // all requests, not only the ones using the URLservice
091                // framework (?)
092                resetHotReloadContext(context);
093                // restore state
094                service.navigate(context);
095                // apply requests parameters after - they may need the state
096                // to be restored first.
097                service.applyRequestParameters(context);
098            }
099        } catch (RuntimeException | SystemException | NotSupportedException e) {
100            handleException(context, e);
101        }
102    }
103
104    @Override
105    public void afterPhase(PhaseEvent event) {
106        // SeamPhaseListener.handleTransactionsAfterPhase will commit the
107        // transaction, so it has to be started using Seam methods.
108    }
109
110    protected void handleException(FacesContext context, Exception e) {
111        try {
112            ExternalContext externalContext = context.getExternalContext();
113            ExceptionHandlingService exceptionHandlingService = Framework.getService(ExceptionHandlingService.class);
114            NuxeoExceptionHandler handler = exceptionHandlingService.getExceptionHandler();
115            handler.handleException((HttpServletRequest) externalContext.getRequest(),
116                    (HttpServletResponse) externalContext.getResponse(), e);
117        } catch (ServletException | IOException e1) {
118            throw new NuxeoException(e1);
119        }
120    }
121
122    /**
123     * Hack trigger of a Seam component that will trigger reset of most Seam components caches, only if dev mode is
124     * enabled
125     * <p>
126     * This is handled here to be done very early, before response is constructed.
127     *
128     * @since 5.6
129     * @see Framework#isDevModeSet()
130     */
131    protected void resetHotReloadContext(FacesContext facesContext) {
132        if (Framework.isDevModeSet()) {
133            try {
134                ExpressionFactory ef = facesContext.getApplication().getExpressionFactory();
135                ELContext context = facesContext.getELContext();
136                String actionBinding = SEAM_HOTRELOAD_TRIGGER_ACTION;
137                MethodExpression action = ef.createMethodExpression(context, actionBinding, String.class,
138                        new Class[] { DocumentView.class });
139                action.invoke(context, new Object[0]);
140            } catch (ELException | NullPointerException e) {
141                String msg = String.format("Error while trying to flush seam context after "
142                        + "a reload, executing method expression '%s'", SEAM_HOTRELOAD_TRIGGER_ACTION);
143                log.error(msg, e);
144            }
145        }
146    }
147}