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 */
022package org.granite.seam21;
023
024import java.lang.reflect.Method;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028
029import javax.servlet.http.HttpServletRequest;
030
031import org.granite.context.GraniteContext;
032import org.granite.logging.Logger;
033import org.granite.messaging.amf.process.AMF3MessageInterceptor;
034import org.granite.messaging.service.ServiceException;
035import org.granite.messaging.webapp.HttpGraniteContext;
036import org.granite.messaging.webapp.HttpServletRequestParamWrapper;
037import org.granite.messaging.webapp.ServletGraniteContext;
038import org.jboss.seam.contexts.Contexts;
039import org.jboss.seam.contexts.ServletLifecycle;
040import org.jboss.seam.core.Conversation;
041import org.jboss.seam.core.ConversationPropagation;
042import org.jboss.seam.core.Manager;
043import org.jboss.seam.international.StatusMessage;
044import org.jboss.seam.international.StatusMessages;
045import org.jboss.seam.servlet.ServletRequestSessionMap;
046import org.jboss.seam.util.Reflections;
047import org.jboss.seam.web.ServletContexts;
048
049import flex.messaging.messages.Message;
050
051
052public 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}