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.seam21;
022
023 import java.lang.reflect.Method;
024 import java.util.Iterator;
025 import java.util.List;
026 import java.util.Map;
027
028 import javax.servlet.http.HttpServletRequest;
029
030 import org.granite.context.GraniteContext;
031 import org.granite.logging.Logger;
032 import org.granite.messaging.amf.process.AMF3MessageInterceptor;
033 import org.granite.messaging.service.ServiceException;
034 import org.granite.messaging.webapp.HttpGraniteContext;
035 import org.granite.messaging.webapp.HttpServletRequestParamWrapper;
036 import org.jboss.seam.contexts.Contexts;
037 import org.jboss.seam.contexts.ServletLifecycle;
038 import org.jboss.seam.core.Conversation;
039 import org.jboss.seam.core.ConversationPropagation;
040 import org.jboss.seam.core.Manager;
041 import org.jboss.seam.international.StatusMessage;
042 import org.jboss.seam.international.StatusMessages;
043 import org.jboss.seam.servlet.ServletRequestSessionMap;
044 import org.jboss.seam.util.Reflections;
045 import org.jboss.seam.web.ServletContexts;
046
047 import flex.messaging.messages.Message;
048
049
050 public class Seam21Interceptor implements AMF3MessageInterceptor {
051
052 private static final Logger log = Logger.getLogger(Seam21Interceptor.class);
053
054 private static final String CONVERSATION_ID = "conversationId";
055 private static final String PARENT_CONVERSATION_ID = "parentConversationId";
056 private static final String IS_LONG_RUNNING_CONVERSATION = "isLongRunningConversation";
057 private static final String WAS_LONG_RUNNING_CONVERSATION_ENDED = "wasLongRunningConversationEnded";
058 private static final String WAS_LONG_RUNNING_CONVERSATION_CREATED = "wasLongRunningConversationCreated";
059 private static final String MESSAGE_HEADER = "MESSAGE_HEADER";
060 private static final String MSG_SEP = ":;:";
061
062
063 /* (non-Javadoc)
064 * @see org.granite.messaging.amf.process.AMF3MessageInterceptor#before(flex.messaging.messages.Message)
065 */
066 public void before(Message amfReqMessage) {
067 if (log.isTraceEnabled())
068 log.trace("Pre processing of request message: %s", amfReqMessage);
069
070 try {
071 GraniteContext context = GraniteContext.getCurrentInstance();
072
073 if (context instanceof HttpGraniteContext) {
074 log.debug("Creating custom HttpServletRequest wrapper");
075 HttpServletRequestParamWrapper request = new HttpServletRequestParamWrapper(((HttpGraniteContext)context).getRequest());
076
077 //Now export the headers - copy the headers to request object
078 exportHeaders(request, amfReqMessage);
079
080 //Time to initialize Seam Context
081 initializeSeamContext(request);
082 }
083 }
084 catch(Exception e) {
085 log.error(e, "Exception while pre processing the request message.");
086 throw new ServiceException("Error while pre processing the request message - " + e.getMessage());
087 }
088 }
089
090 /* (non-Javadoc)
091 * @see org.granite.messaging.amf.process.AMF3MessageInterceptor#after(flex.messaging.messages.Message, flex.messaging.messages.Message)
092 */
093 public void after(Message amfReqMessage, Message amfRespMessage) {
094 try {
095 if (log.isTraceEnabled())
096 log.trace("Post processing of response message: %s", amfReqMessage);
097
098 if (GraniteContext.getCurrentInstance() instanceof HttpGraniteContext) {
099 try {
100 //Now time to set back the headers, always has one body
101 importHeaders(amfRespMessage);
102 }
103 finally {
104 //Time to destroy the seam context
105 destroySeamContext();
106 }
107 }
108 }
109 catch (Exception e) {
110 log.error(e, "Exception while post processing the response message.");
111 throw new ServiceException("Error while post processing the response message - " + e.getMessage());
112 }
113 }
114
115 /**
116 * Reads the AMF request header and populate them in the request object
117 * @param request - HttpServletRequestParamWrapper
118 * @param amf3RequestMessage
119 */
120 protected void exportHeaders(HttpServletRequestParamWrapper request, Message amf3RequestMessage) {
121 //Read the headers from first body
122 Map<String, Object> headerMap = amf3RequestMessage.getHeaders();
123 if (headerMap != null && headerMap.size() > 0) {
124 Iterator<String> headerKeys = headerMap.keySet().iterator();
125 while (headerKeys.hasNext()) {
126 String key = headerKeys.next();
127 String value = headerMap.get(key) == null ? null : headerMap.get(key).toString();
128 if( value != null) {
129 request.setParameter(key, value);
130 }
131 }
132 }
133 }
134
135 /**
136 * Update the AMF response message with the conversationId and other parameters.
137 * @param amf3ResponseMessage
138 */
139 protected void importHeaders(Message amf3ResponseMessage) {
140 if (amf3ResponseMessage != null) {
141 Conversation conversation = Conversation.instance();
142 if (Contexts.getEventContext().isSet("org.granite.tide.conversation.wasLongRunning") && !conversation.isLongRunning())
143 amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_ENDED, true);
144
145 if (Contexts.getEventContext().isSet("org.granite.tide.conversation.wasCreated") && conversation.isLongRunning())
146 amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_CREATED, true);
147
148 log.debug("CONVERSATION_ID: %s", conversation.getId());
149 amf3ResponseMessage.setHeader(CONVERSATION_ID, conversation.getId());
150
151 log.debug("PARENT_CONVERSATION_ID: %s", conversation.getParentId());
152 amf3ResponseMessage.setHeader(PARENT_CONVERSATION_ID, conversation.getParentId());
153
154 log.debug("IS_LONG_RUNNING_CONVERSATION: %s", conversation.isLongRunning());
155 amf3ResponseMessage.setHeader(IS_LONG_RUNNING_CONVERSATION, conversation.isLongRunning());
156
157 log.debug("Processing the Status messages.");
158 processStatusMessages(amf3ResponseMessage);
159 }
160 }
161
162 /**
163 * Initialize the Seam Context
164 * @param request - HttpServletRequest
165 */
166 protected void initializeSeamContext(HttpServletRequest request) {
167 log.debug("beginning request");
168
169 ServletLifecycle.beginRequest(request);
170 ServletContexts.instance().setRequest(request);
171
172 // Force "conversationId" as parameter for GraniteDS requests
173 Manager.instance().setConversationIdParameter(CONVERSATION_ID);
174 restoreConversationId();
175 String conversationId = ConversationPropagation.instance().getConversationId();
176 Manager.instance().restoreConversation();
177 ServletLifecycle.resumeConversation(request);
178 handleConversationPropagation();
179 if (conversationId != null && !conversationId.equals(Manager.instance().getCurrentConversationId())) {
180 log.debug("Changed current conversation from %s to %s", Manager.instance().getCurrentConversationId(), conversationId);
181 Manager.instance().updateCurrentConversationId(conversationId);
182 }
183 else if (conversationId != null)
184 log.debug("Restored conversation %s", conversationId);
185 if (Manager.instance().isLongRunningConversation())
186 Contexts.getEventContext().set("org.granite.tide.conversation.wasLongRunning", true);
187
188 // Force creation of the session
189 if (request.getSession(false) == null)
190 request.getSession(true);
191
192 if (Boolean.TRUE.toString().equals(request.getParameter("org.granite.tide.isFirstCall")))
193 Contexts.getSessionContext().set("org.granite.tide.isFirstCall", Boolean.TRUE);
194
195 if (Boolean.TRUE.toString().equals(request.getParameter("org.granite.tide.isFirstConversationCall")) && Manager.instance().isLongRunningConversation())
196 Contexts.getConversationContext().set("org.granite.tide.isFirstConversationCall", Boolean.TRUE);
197 }
198
199 /**
200 * Destroy the Seam Context
201 * @param request - HttpServletRequest
202 */
203 private void destroySeamContext() {
204 // Flush current conversation metadata if needed
205 if (Manager.instance().isLongRunningConversation()) {
206 Conversation conversation = Conversation.instance();
207 try {
208 Method method = conversation.getClass().getDeclaredMethod("flush");
209 method.setAccessible(true);
210 Reflections.invoke(method, conversation);
211 }
212 catch (Exception e) {
213 log.error("Could not flush current long-running conversation " + conversation.getId(), e);
214 }
215 }
216
217 //Retrieve the stored request from Seam Servlet Context
218 Manager.instance().endRequest( new ServletRequestSessionMap(ServletContexts.getInstance().getRequest()) );
219 ServletLifecycle.endRequest(ServletContexts.getInstance().getRequest());
220
221 log.debug("ended request");
222 }
223
224
225 /**
226 * Process the Status messages and sets to the response header.
227 * @param amf3ResponseMessage
228 */
229 protected void processStatusMessages(Message amf3ResponseMessage) {
230 if (amf3ResponseMessage != null) {
231 StatusMessages statusMessages = StatusMessages.instance();
232 if (statusMessages == null)
233 return;
234
235 try {
236 // Execute and get the messages (once again reflection hack to use protected methods)
237 Method m = StatusMessages.class.getDeclaredMethod("doRunTasks");
238 m.setAccessible(true);
239 m.invoke(statusMessages);
240
241 Method m2 = StatusMessages.class.getDeclaredMethod("getMessages");
242 m2.setAccessible(true);
243 @SuppressWarnings("unchecked")
244 List<StatusMessage> messages = (List<StatusMessage>)m2.invoke(statusMessages);
245
246 log.debug("Found Messages: %b", !messages.isEmpty());
247 StringBuilder messagesBuf = new StringBuilder();
248 for (StatusMessage msg : messages) {
249 log.debug("StatusMessage %s - %s", msg.getDetail(), msg.getSummary());
250 messagesBuf.append(msg.getSummary());
251 messagesBuf.append(MSG_SEP);
252 }
253
254 String messageStr = messagesBuf.toString().trim();
255
256 if (messageStr.length() > 0) {
257 messageStr = messageStr.substring(0, messageStr.lastIndexOf(MSG_SEP));
258 amf3ResponseMessage.setHeader(MESSAGE_HEADER, messageStr);
259 }
260 }
261 catch (Exception e) {
262 log.error("Could not get status messages", e);
263 }
264 }
265 }
266
267 /**
268 *
269 */
270 protected void handleConversationPropagation() {
271 Manager.instance().handleConversationPropagation( ServletContexts.getInstance().getRequest().getParameterMap() );
272 }
273
274 /**
275 *
276 */
277 protected void restoreConversationId() {
278 ConversationPropagation.instance().restoreConversationId( ServletContexts.getInstance().getRequest().getParameterMap() );
279 }
280 }