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