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    }