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
021 package org.granite.seam;
022
023 import java.lang.reflect.Method;
024 import java.util.Iterator;
025 import java.util.Map;
026
027 import javax.faces.FactoryFinder;
028 import javax.faces.application.FacesMessage;
029 import javax.faces.context.FacesContext;
030 import javax.faces.context.FacesContextFactory;
031 import javax.faces.lifecycle.Lifecycle;
032 import javax.faces.lifecycle.LifecycleFactory;
033 import javax.servlet.ServletException;
034 import javax.servlet.http.HttpServletRequest;
035
036 import org.granite.context.GraniteContext;
037 import org.granite.logging.Logger;
038 import org.granite.messaging.amf.process.AMF3MessageInterceptor;
039 import org.granite.messaging.service.ServiceException;
040 import org.granite.messaging.webapp.HttpGraniteContext;
041 import org.granite.messaging.webapp.HttpServletRequestParamWrapper;
042 import org.jboss.seam.contexts.Contexts;
043 import org.jboss.seam.contexts.ServletLifecycle;
044 import org.jboss.seam.core.Conversation;
045 import org.jboss.seam.core.ConversationPropagation;
046 import org.jboss.seam.core.Manager;
047 import org.jboss.seam.faces.FacesMessages;
048 import org.jboss.seam.servlet.ServletRequestSessionMap;
049 import org.jboss.seam.util.Reflections;
050 import org.jboss.seam.web.ServletContexts;
051
052 import flex.messaging.messages.Message;
053
054
055 public 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 }