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