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    }