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}