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    }