001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2014 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.seam; 023 024import java.lang.reflect.Method; 025import java.util.Iterator; 026import java.util.Map; 027 028import javax.faces.FactoryFinder; 029import javax.faces.application.FacesMessage; 030import javax.faces.context.FacesContext; 031import javax.faces.context.FacesContextFactory; 032import javax.faces.lifecycle.Lifecycle; 033import javax.faces.lifecycle.LifecycleFactory; 034import javax.servlet.ServletException; 035import javax.servlet.http.HttpServletRequest; 036 037import org.granite.context.GraniteContext; 038import org.granite.logging.Logger; 039import org.granite.messaging.amf.process.AMF3MessageInterceptor; 040import org.granite.messaging.service.ServiceException; 041import org.granite.messaging.webapp.HttpGraniteContext; 042import org.granite.messaging.webapp.HttpServletRequestParamWrapper; 043import org.jboss.seam.contexts.Contexts; 044import org.jboss.seam.contexts.ServletLifecycle; 045import org.jboss.seam.core.Conversation; 046import org.jboss.seam.core.ConversationPropagation; 047import org.jboss.seam.core.Manager; 048import org.jboss.seam.faces.FacesMessages; 049import org.jboss.seam.servlet.ServletRequestSessionMap; 050import org.jboss.seam.util.Reflections; 051import org.jboss.seam.web.ServletContexts; 052 053import flex.messaging.messages.Message; 054 055 056public class SeamInterceptor implements AMF3MessageInterceptor { 057 058 private static final Logger log = Logger.getLogger(SeamInterceptor.class); 059 060 private static final String CONVERSATION_ID = "conversationId"; 061 private static final String PARENT_CONVERSATION_ID = "parentConversationId"; 062 private static final String IS_LONG_RUNNING_CONVERSATION = "isLongRunningConversation"; 063 private static final String WAS_LONG_RUNNING_CONVERSATION_CREATED = "wasLongRunningConversationCreated"; 064 private static final String WAS_LONG_RUNNING_CONVERSATION_ENDED = "wasLongRunningConversationEnded"; 065 private static final String MESSAGE_HEADER = "MESSAGE_HEADER"; 066 private static final String MSG_SEP = ":;:"; 067 068 /* (non-Javadoc) 069 * @see org.granite.messaging.amf.process.AMF3MessageInterceptor#before(flex.messaging.messages.Message) 070 */ 071 public void before(Message amfReqMessage) { 072 if (log.isTraceEnabled()) 073 log.trace("Pre processing of request message: %s", amfReqMessage); 074 075 try { 076 GraniteContext context = GraniteContext.getCurrentInstance(); 077 078 if (context instanceof HttpGraniteContext) { 079 log.debug("Creating custom HttpServletRequest wrapper"); 080 HttpServletRequestParamWrapper request = new HttpServletRequestParamWrapper(((HttpGraniteContext)context).getRequest()); 081 082 //Initialize the FacesContext for each body request 083 initializeFacesContext(request, (HttpGraniteContext)context); 084 085 //Now export the headers - copy the headers to request object 086 exportHeaders(request, amfReqMessage); 087 088 //Time to initialize Seam Context 089 initializeSeamContext(request); 090 } 091 } 092 catch(Exception e) { 093 log.error(e, "Exception while pre processing the request message."); 094 throw new ServiceException("Error while pre processing the request message - " + e.getMessage()); 095 } 096 } 097 098 /* (non-Javadoc) 099 * @see org.granite.messaging.amf.process.AMF3MessageInterceptor#after(flex.messaging.messages.Message, flex.messaging.messages.Message) 100 */ 101 public void after(Message amfReqMessage, Message amfRespMessage) { 102 try { 103 if (log.isTraceEnabled()) 104 log.trace("Post processing of response message: %s", amfReqMessage); 105 106 if (GraniteContext.getCurrentInstance() instanceof HttpGraniteContext) { 107 try { 108 //Now time to set back the headers, always has one body 109 importHeaders(amfRespMessage); 110 } 111 finally { 112 //Time to destroy the seam context 113 destroySeamContext(); 114 } 115 } 116 } 117 catch (Exception e) { 118 log.error(e, "Exception while post processing the response message."); 119 throw new ServiceException("Error while post processing the response message - " + e.getMessage()); 120 } 121 finally { 122 //Release the FacesContext - clears messages etc., 123 FacesContext context = FacesContext.getCurrentInstance(); 124 if (context != null) 125 context.release(); 126 } 127 } 128 129 /** 130 * Reads the AMF request header and populate them in the request object 131 * @param request - HttpServletRequestParamWrapper 132 * @param amf3RequestMessage 133 */ 134 private void exportHeaders(HttpServletRequestParamWrapper request, Message amf3RequestMessage) { 135 //Read the headers from first body 136 Map<String, Object> headerMap = amf3RequestMessage.getHeaders(); 137 if (headerMap != null && headerMap.size() > 0) { 138 Iterator<String> headerKeys = headerMap.keySet().iterator(); 139 while (headerKeys.hasNext()) { 140 String key = headerKeys.next(); 141 String value = headerMap.get(key) == null ? null : headerMap.get(key).toString(); 142 if( value != null) { 143 request.setParameter(key, value); 144 } 145 } 146 } 147 } 148 149 /** 150 * Update the AMF response message with the conversationId and other parameters. 151 * @param amf3ResponseMessage 152 */ 153 private void importHeaders(Message amf3ResponseMessage) { 154 if (amf3ResponseMessage != null) { 155 Conversation conversation = Conversation.instance(); 156 if (Contexts.getEventContext().isSet("org.granite.tide.conversation.wasLongRunning") && !conversation.isLongRunning()) 157 amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_ENDED, true); 158 159 if (Contexts.getEventContext().isSet("org.granite.tide.conversation.wasCreated") && conversation.isLongRunning()) 160 amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_CREATED, true); 161 162 log.debug("CONVERSATION_ID: %s", conversation.getId()); 163 amf3ResponseMessage.setHeader(CONVERSATION_ID, conversation.getId()); 164 165 log.debug("PARENT_CONVERSATION_ID: %s", conversation.getParentId()); 166 amf3ResponseMessage.setHeader(PARENT_CONVERSATION_ID, conversation.getParentId()); 167 168 log.debug("IS_LONG_RUNNING_CONVERSATION: %s", conversation.isLongRunning()); 169 amf3ResponseMessage.setHeader(IS_LONG_RUNNING_CONVERSATION, conversation.isLongRunning()); 170 171 log.debug("Processing the Faces messages."); 172 processFacesMessages(amf3ResponseMessage); 173 } 174 } 175 176 /** 177 * Process the faces messages and sets to the response header. 178 * @param amf3ResponseMessage 179 */ 180 private void processFacesMessages(Message amf3ResponseMessage) { 181 if (amf3ResponseMessage != null) { 182 //Prepare for the messages. First step is convert the tasks to Seam FacesMessages 183 FacesMessages.afterPhase(); 184 185 //Second step is add the Seam FacesMessages to JSF FacesContext Messages 186 FacesMessages.instance().beforeRenderResponse(); 187 188 //TODO - Work on how effective we can pass the messages to Flex side 189 Iterator<FacesMessage> messageItr = FacesContext.getCurrentInstance().getMessages(); 190 log.debug("Found Messages: %b", messageItr.hasNext()); 191 StringBuilder messagesBuf = new StringBuilder(); 192 while (messageItr.hasNext()) { 193 FacesMessage msg = messageItr.next(); 194 log.debug("FacesMessages %s - %s", msg.getDetail(), msg.getSummary()); 195 messagesBuf.append(msg.getSummary()); 196 messagesBuf.append(MSG_SEP); 197 } 198 199 String messageStr = messagesBuf.toString().trim(); 200 201 if (messageStr.length() > 0) { 202 messageStr = messageStr.substring(0, messageStr.lastIndexOf(MSG_SEP)); 203 amf3ResponseMessage.setHeader(MESSAGE_HEADER, messageStr); 204 } 205 } 206 } 207 208 /** 209 * Initialize the Seam Context 210 * @param request - HttpServletRequest 211 */ 212 private void initializeSeamContext(HttpServletRequest request) { 213 log.debug("beginning request"); 214 215 ServletLifecycle.beginRequest(request); 216 ServletContexts.instance().setRequest(request); 217 218 Manager.instance().setConversationIdParameter(CONVERSATION_ID); 219 restoreConversationId(); 220 String conversationId = ConversationPropagation.instance().getConversationId(); 221 Manager.instance().restoreConversation(); 222 ServletLifecycle.resumeConversation(request); 223 handleConversationPropagation(); 224 if (conversationId != null && !conversationId.equals(Manager.instance().getCurrentConversationId())) 225 Manager.instance().updateCurrentConversationId(conversationId); 226 if (Manager.instance().isLongRunningConversation()) 227 Contexts.getEventContext().set("org.granite.tide.conversation.wasLongRunning", true); 228 229 // Force creation of the session 230 if (request.getSession(false) == null) 231 request.getSession(true); 232 233 if (Boolean.TRUE.toString().equals(request.getParameter("org.granite.tide.isFirstCall"))) 234 Contexts.getSessionContext().set("org.granite.tide.isFirstCall", Boolean.TRUE); 235 236 if (Boolean.TRUE.toString().equals(request.getParameter("org.granite.tide.isFirstConversationCall")) && Manager.instance().isLongRunningConversation()) 237 Contexts.getConversationContext().set("org.granite.tide.isFirstConversationCall", Boolean.TRUE); 238 } 239 240 /** 241 * Destroy the Seam Context 242 * @param request - HttpServletRequest 243 */ 244 private void destroySeamContext() { 245 // Flush current conversation metadata if needed 246 if (Manager.instance().isLongRunningConversation()) { 247 Conversation conversation = Conversation.instance(); 248 try { 249 Method method = conversation.getClass().getDeclaredMethod("flush"); 250 method.setAccessible(true); 251 Reflections.invoke(method, conversation); 252 } 253 catch (Exception e) { 254 log.error("Could not flush current long-running conversation " + conversation.getId(), e); 255 } 256 } 257 258 //Retrieve the stored request from Seam Servlet Context 259 Manager.instance().endRequest( new ServletRequestSessionMap(ServletContexts.getInstance().getRequest()) ); 260 ServletLifecycle.endRequest(ServletContexts.getInstance().getRequest()); 261 262 log.debug("ended request"); 263 } 264 265 /** 266 * Returns facesContext if already exists otherwise creates a new one. 267 * @param request - HttpServletRequest 268 * @param context - HttpGraniteContext 269 * @return FacesContext 270 * @throws ServletException 271 */ 272 private FacesContext initializeFacesContext(HttpServletRequest request, HttpGraniteContext context) throws ServletException { 273 try { 274 FacesContext facesContext = FacesContext.getCurrentInstance(); 275 276 if (facesContext != null) 277 return facesContext; 278 279 //Use the FactoryFinder to find the Lifecycle object 280 FacesContextFactory contextFactory = (FacesContextFactory) 281 FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); 282 LifecycleFactory lifecycleFactory = (LifecycleFactory) 283 FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); 284 Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE); 285 286 facesContext = contextFactory.getFacesContext(context.getServletContext(), request, context.getResponse(), lifecycle); 287 //Set as current FacesContext 288 InnerFacesContext.setFacesContextAsCurrentInstance(facesContext); 289 290 return facesContext; 291 } 292 catch (Exception e) { 293 throw new ServletException(e); 294 } 295 } 296 297 /** 298 * 299 */ 300 protected void handleConversationPropagation() { 301 Manager.instance().handleConversationPropagation( ServletContexts.getInstance().getRequest().getParameterMap() ); 302 } 303 304 /** 305 * 306 */ 307 protected void restoreConversationId() { 308 ConversationPropagation.instance().restoreConversationId( ServletContexts.getInstance().getRequest().getParameterMap() ); 309 } 310 311 /** 312 * Create a inner class to make a call to setCurrentInstance. 313 */ 314 private abstract static class InnerFacesContext extends FacesContext { 315 protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) { 316 FacesContext.setCurrentInstance(facesContext); 317 } 318 } 319}