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