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}