/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.seam21; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.granite.context.GraniteContext; import org.granite.logging.Logger; import org.granite.messaging.amf.process.AMF3MessageInterceptor; import org.granite.messaging.service.ServiceException; import org.granite.messaging.webapp.HttpGraniteContext; import org.granite.messaging.webapp.HttpServletRequestParamWrapper; import org.granite.messaging.webapp.ServletGraniteContext; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.contexts.ServletLifecycle; import org.jboss.seam.core.Conversation; import org.jboss.seam.core.ConversationPropagation; import org.jboss.seam.core.Manager; import org.jboss.seam.international.StatusMessage; import org.jboss.seam.international.StatusMessages; import org.jboss.seam.servlet.ServletRequestSessionMap; import org.jboss.seam.util.Reflections; import org.jboss.seam.web.ServletContexts; import flex.messaging.messages.Message; public class Seam21Interceptor implements AMF3MessageInterceptor { private static final Logger log = Logger.getLogger(Seam21Interceptor.class); private static final String CONVERSATION_ID = "conversationId"; private static final String PARENT_CONVERSATION_ID = "parentConversationId"; private static final String IS_LONG_RUNNING_CONVERSATION = "isLongRunningConversation"; private static final String WAS_LONG_RUNNING_CONVERSATION_ENDED = "wasLongRunningConversationEnded"; private static final String WAS_LONG_RUNNING_CONVERSATION_CREATED = "wasLongRunningConversationCreated"; private static final String MESSAGE_HEADER = "MESSAGE_HEADER"; private static final String MSG_SEP = ":;:"; public void before(Message amfReqMessage) { if (log.isTraceEnabled()) log.trace("Pre processing of request message: %s", amfReqMessage); try { GraniteContext context = GraniteContext.getCurrentInstance(); if (context instanceof ServletGraniteContext) { log.debug("Creating custom HttpServletRequest wrapper"); HttpServletRequestParamWrapper request = new HttpServletRequestParamWrapper(((HttpGraniteContext)context).getRequest()); //Now export the headers - copy the headers to request object exportHeaders(request, amfReqMessage); //Time to initialize Seam Context initializeSeamContext(request); } } catch(Exception e) { log.error(e, "Exception while pre processing the request message."); throw new ServiceException("Error while pre processing the request message - " + e.getMessage()); } } public void after(Message amfReqMessage, Message amfRespMessage) { try { if (log.isTraceEnabled()) log.trace("Post processing of response message: %s", amfReqMessage); if (GraniteContext.getCurrentInstance() instanceof ServletGraniteContext) { try { //Now time to set back the headers, always has one body importHeaders(amfRespMessage); } finally { //Time to destroy the seam context destroySeamContext(); } } } catch (Exception e) { log.error(e, "Exception while post processing the response message."); throw new ServiceException("Error while post processing the response message - " + e.getMessage()); } } /** * Reads the AMF request header and populate them in the request object * @param request - HttpServletRequestParamWrapper * @param amf3RequestMessage */ protected void exportHeaders(HttpServletRequestParamWrapper request, Message amf3RequestMessage) { //Read the headers from first body Map<String, Object> headerMap = amf3RequestMessage.getHeaders(); if (headerMap != null && headerMap.size() > 0) { Iterator<String> headerKeys = headerMap.keySet().iterator(); while (headerKeys.hasNext()) { String key = headerKeys.next(); String value = headerMap.get(key) == null ? null : headerMap.get(key).toString(); if( value != null) { request.setParameter(key, value); } } } } /** * Update the AMF response message with the conversationId and other parameters. * @param amf3ResponseMessage */ protected void importHeaders(Message amf3ResponseMessage) { if (amf3ResponseMessage != null) { Conversation conversation = Conversation.instance(); if (Contexts.getEventContext().isSet("org.granite.tide.conversation.wasLongRunning") && !conversation.isLongRunning()) amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_ENDED, true); if (Contexts.getEventContext().isSet("org.granite.tide.conversation.wasCreated") && conversation.isLongRunning()) amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_CREATED, true); log.debug("CONVERSATION_ID: %s", conversation.getId()); amf3ResponseMessage.setHeader(CONVERSATION_ID, conversation.getId()); log.debug("PARENT_CONVERSATION_ID: %s", conversation.getParentId()); amf3ResponseMessage.setHeader(PARENT_CONVERSATION_ID, conversation.getParentId()); log.debug("IS_LONG_RUNNING_CONVERSATION: %s", conversation.isLongRunning()); amf3ResponseMessage.setHeader(IS_LONG_RUNNING_CONVERSATION, conversation.isLongRunning()); log.debug("Processing the Status messages."); processStatusMessages(amf3ResponseMessage); } } /** * Initialize the Seam Context * @param request - HttpServletRequest */ protected void initializeSeamContext(HttpServletRequest request) { log.debug("beginning request"); ServletLifecycle.beginRequest(request); ServletContexts.instance().setRequest(request); // Force "conversationId" as parameter for GraniteDS requests Manager.instance().setConversationIdParameter(CONVERSATION_ID); restoreConversationId(); String conversationId = ConversationPropagation.instance().getConversationId(); Manager.instance().restoreConversation(); ServletLifecycle.resumeConversation(request); handleConversationPropagation(); if (conversationId != null && !conversationId.equals(Manager.instance().getCurrentConversationId())) { log.debug("Changed current conversation from %s to %s", Manager.instance().getCurrentConversationId(), conversationId); Manager.instance().updateCurrentConversationId(conversationId); } else if (conversationId != null) log.debug("Restored conversation %s", conversationId); if (Manager.instance().isLongRunningConversation()) Contexts.getEventContext().set("org.granite.tide.conversation.wasLongRunning", true); // Force creation of the session if (request.getSession(false) == null) request.getSession(true); if (Boolean.TRUE.toString().equals(request.getParameter("org.granite.tide.isFirstCall"))) Contexts.getSessionContext().set("org.granite.tide.isFirstCall", Boolean.TRUE); if (Boolean.TRUE.toString().equals(request.getParameter("org.granite.tide.isFirstConversationCall")) && Manager.instance().isLongRunningConversation()) Contexts.getConversationContext().set("org.granite.tide.isFirstConversationCall", Boolean.TRUE); } /** * Destroy the Seam Context * @param request - HttpServletRequest */ private void destroySeamContext() { // Flush current conversation metadata if needed if (Manager.instance().isLongRunningConversation()) { Conversation conversation = Conversation.instance(); try { Method method = conversation.getClass().getDeclaredMethod("flush"); method.setAccessible(true); Reflections.invoke(method, conversation); } catch (Exception e) { log.error("Could not flush current long-running conversation " + conversation.getId(), e); } } //Retrieve the stored request from Seam Servlet Context Manager.instance().endRequest( new ServletRequestSessionMap(ServletContexts.getInstance().getRequest()) ); ServletLifecycle.endRequest(ServletContexts.getInstance().getRequest()); log.debug("ended request"); } /** * Process the Status messages and sets to the response header. * @param amf3ResponseMessage */ protected void processStatusMessages(Message amf3ResponseMessage) { if (amf3ResponseMessage != null) { StatusMessages statusMessages = StatusMessages.instance(); if (statusMessages == null) return; try { // Execute and get the messages (once again reflection hack to use protected methods) Method m = StatusMessages.class.getDeclaredMethod("doRunTasks"); m.setAccessible(true); m.invoke(statusMessages); Method m2 = StatusMessages.class.getDeclaredMethod("getMessages"); m2.setAccessible(true); @SuppressWarnings("unchecked") List<StatusMessage> messages = (List<StatusMessage>)m2.invoke(statusMessages); log.debug("Found Messages: %b", !messages.isEmpty()); StringBuilder messagesBuf = new StringBuilder(); for (StatusMessage msg : messages) { log.debug("StatusMessage %s - %s", msg.getDetail(), msg.getSummary()); messagesBuf.append(msg.getSummary()); messagesBuf.append(MSG_SEP); } String messageStr = messagesBuf.toString().trim(); if (messageStr.length() > 0) { messageStr = messageStr.substring(0, messageStr.lastIndexOf(MSG_SEP)); amf3ResponseMessage.setHeader(MESSAGE_HEADER, messageStr); } } catch (Exception e) { log.error("Could not get status messages", e); } } } /** * */ protected void handleConversationPropagation() { Manager.instance().handleConversationPropagation( ServletContexts.getInstance().getRequest().getParameterMap() ); } /** * */ protected void restoreConversationId() { ConversationPropagation.instance().restoreConversationId( ServletContexts.getInstance().getRequest().getParameterMap() ); } }