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