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