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.cdi;
023
024import java.lang.reflect.Field;
025import java.lang.reflect.Method;
026import java.util.Iterator;
027import java.util.Map;
028
029import javax.enterprise.context.Conversation;
030import javax.enterprise.inject.spi.Bean;
031import javax.enterprise.inject.spi.BeanManager;
032import javax.servlet.ServletRequestEvent;
033import javax.servlet.ServletRequestListener;
034
035import org.granite.context.GraniteContext;
036import org.granite.logging.Logger;
037import org.granite.messaging.amf.process.AMF3MessageInterceptor;
038import org.granite.messaging.service.ServiceException;
039import org.granite.messaging.webapp.HttpGraniteContext;
040import org.granite.messaging.webapp.HttpServletRequestParamWrapper;
041import org.granite.tide.cdi.ConversationState;
042import org.granite.tide.cdi.EventState;
043import org.granite.tide.cdi.SessionState;
044import org.granite.util.TypeUtil;
045
046import flex.messaging.messages.Message;
047
048
049public class CDIInterceptor implements AMF3MessageInterceptor {
050        
051        private static final Logger log = Logger.getLogger(CDIInterceptor.class);
052
053    private static final String CONVERSATION_ID = "conversationId";
054    private static final String IS_LONG_RUNNING_CONVERSATION = "isLongRunningConversation";
055    private static final String WAS_LONG_RUNNING_CONVERSATION_CREATED = "wasLongRunningConversationCreated";
056    private static final String WAS_LONG_RUNNING_CONVERSATION_ENDED = "wasLongRunningConversationEnded";
057        
058    private final CDIConversationManager conversationManager;
059    private final ServletRequestListener requestListener;
060
061
062    public CDIInterceptor() {
063        CDIConversationManager conversationManager = null;
064        ServletRequestListener listener = null;
065        try {
066                Thread.currentThread().getContextClassLoader().loadClass("org.jboss.weld.context.http.HttpConversationContext");
067                conversationManager = TypeUtil.newInstance("org.granite.cdi.Weld11ConversationManager", CDIConversationManager.class);
068            listener = TypeUtil.newInstance("org.jboss.weld.servlet.WeldListener", ServletRequestListener.class);
069                log.info("Detected JBoss Weld 1.1+");
070        }
071        catch (Exception e) {
072            try {
073                conversationManager = null;
074                // Hacky way to initialize a request listener for OWB
075                listener = TypeUtil.newInstance("org.apache.webbeans.servlet.WebBeansConfigurationListener", ServletRequestListener.class);
076                Field wbcField = listener.getClass().getDeclaredField("webBeansContext");
077                wbcField.setAccessible(true);
078                Object webBeansContext = wbcField.get(listener);
079                Field lcField = listener.getClass().getDeclaredField("lifeCycle");
080                lcField.setAccessible(true);
081                Method wbcGetService = webBeansContext.getClass().getMethod("getService", Class.class);
082                Object lifecycle = wbcGetService.invoke(webBeansContext, TypeUtil.forName("org.apache.webbeans.spi.ContainerLifecycle"));
083                lcField.set(listener, lifecycle);
084                log.info("Detected Apache OpenWebBeans, conversation support disabled");
085            }
086            catch (Exception f2) {
087                log.warn("Unsupported CDI container, conversation support disabled");
088            }
089        }
090        this.conversationManager = conversationManager;
091        this.requestListener = listener;
092    }
093    
094    
095        private static final String MESSAGECOUNT_ATTR = CDIInterceptor.class.getName() + "_messageCount";
096        private static final String REQUESTWRAPPER_ATTR = CDIInterceptor.class.getName() + "_requestWrapper";
097    
098    
099        public void before(Message amf3RequestMessage) {
100                if (log.isTraceEnabled())
101                        log.trace("Pre processing of request message: %s", amf3RequestMessage);
102                
103                try {
104                        GraniteContext context = GraniteContext.getCurrentInstance();
105                        
106                        if (context instanceof HttpGraniteContext) {
107                                HttpGraniteContext httpContext = ((HttpGraniteContext)context);
108
109                if (requestListener != null) {
110                    Integer wrapCount = (Integer)httpContext.getRequest().getAttribute(MESSAGECOUNT_ATTR);
111                    if (wrapCount == null) {
112                        log.debug("Clearing default container request context");
113                        ServletRequestEvent event = new ServletRequestEvent(httpContext.getServletContext(), httpContext.getRequest());
114                        requestListener.requestDestroyed(event);
115                        httpContext.getRequest().setAttribute(MESSAGECOUNT_ATTR, 1);
116                    }
117                    else
118                        httpContext.getRequest().setAttribute(MESSAGECOUNT_ATTR, wrapCount+1);
119
120                    log.debug("Initializing wrapped AMF request");
121
122                    HttpServletRequestParamWrapper requestWrapper = new HttpServletRequestParamWrapper(httpContext.getRequest());
123                    httpContext.getRequest().setAttribute(REQUESTWRAPPER_ATTR, requestWrapper);
124
125                    // Now export the headers - copy the headers to request object
126                    Map<String, Object> headerMap = amf3RequestMessage.getHeaders();
127                    if (headerMap != null && headerMap.size() > 0) {
128                        Iterator<String> headerKeys = headerMap.keySet().iterator();
129                        while (headerKeys.hasNext()) {
130                            String key = headerKeys.next();
131                            String value = headerMap.get(key) == null ? null : headerMap.get(key).toString();
132                            if (value != null)
133                                requestWrapper.setParameter(key, value);
134                        }
135                    }
136
137                    ServletRequestEvent event = new ServletRequestEvent(((HttpGraniteContext)context).getServletContext(), requestWrapper);
138                    requestListener.requestInitialized(event);
139                }
140
141                BeanManager beanManager = CDIUtils.lookupBeanManager(((HttpGraniteContext)context).getServletContext());
142
143                if (conversationManager != null) {
144                    // Initialize CDI conversation context
145                    String conversationId = (String)amf3RequestMessage.getHeader(CONVERSATION_ID);
146
147                    Conversation conversation = conversationManager.initConversation(beanManager, conversationId);
148
149                    @SuppressWarnings("unchecked")
150                    Bean<EventState> eventBean = (Bean<EventState>)beanManager.getBeans(EventState.class).iterator().next();
151                    EventState eventState = (EventState)beanManager.getReference(eventBean, EventState.class, beanManager.createCreationalContext(eventBean));
152                    if (!conversation.isTransient())
153                        eventState.setWasLongRunning(true);
154
155                    if (conversationId != null && conversation.isTransient()) {
156                        log.debug("Starting conversation " + conversationId);
157                        conversation.begin(conversationId);
158                    }
159
160                    if (Boolean.TRUE.toString().equals(amf3RequestMessage.getHeader("org.granite.tide.isFirstConversationCall")) && !conversation.isTransient()) {
161                        @SuppressWarnings("unchecked")
162                        Bean<ConversationState> csBean = (Bean<ConversationState>)beanManager.getBeans(ConversationState.class).iterator().next();
163                        ((ConversationState)beanManager.getReference(csBean, ConversationState.class, beanManager.createCreationalContext(csBean))).setFirstCall(true);
164                    }
165                }
166
167                if (Boolean.TRUE.toString().equals(amf3RequestMessage.getHeader("org.granite.tide.isFirstCall"))) {
168                    @SuppressWarnings("unchecked")
169                    Bean<SessionState> ssBean = (Bean<SessionState>)beanManager.getBeans(SessionState.class).iterator().next();
170                    ((SessionState)beanManager.getReference(ssBean, SessionState.class, beanManager.createCreationalContext(ssBean))).setFirstCall(true);
171                }
172                        }
173                }
174                catch(Exception e) {
175            log.error(e, "Exception while pre processing the request message.");
176            throw new ServiceException("Error while pre processing the request message - " + e.getMessage());
177                }
178        }
179
180
181        public void after(Message amf3RequestMessage, Message amf3ResponseMessage) {            
182                try {
183                        if (log.isTraceEnabled())
184                                log.trace("Post processing of response message: %s", amf3ResponseMessage);
185
186                        GraniteContext context = GraniteContext.getCurrentInstance();
187                        
188                        if (context instanceof HttpGraniteContext) {
189                            BeanManager beanManager = CDIUtils.lookupBeanManager(((HttpGraniteContext)context).getServletContext());
190
191                                try {
192                                        // Add conversation management headers to response
193                                        if (conversationManager != null && amf3ResponseMessage != null) {
194                                            @SuppressWarnings("unchecked")
195                                            Bean<Conversation> conversationBean = (Bean<Conversation>)beanManager.getBeans(Conversation.class).iterator().next();
196                                            Conversation conversation = (Conversation)beanManager.getReference(conversationBean, Conversation.class, beanManager.createCreationalContext(conversationBean));
197                                            
198                                            @SuppressWarnings("unchecked")
199                                            Bean<EventState> eventBean = (Bean<EventState>)beanManager.getBeans(EventState.class).iterator().next();
200                                            EventState eventState = (EventState)beanManager.getReference(eventBean, EventState.class, beanManager.createCreationalContext(eventBean));
201                                            if (eventState.wasLongRunning() && !conversation.isTransient())
202                                                amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_ENDED, true);
203                                                
204                                    if (eventState.wasCreated() && !conversation.isTransient())
205                                        amf3ResponseMessage.setHeader(WAS_LONG_RUNNING_CONVERSATION_CREATED, true);
206                                    
207                                    amf3ResponseMessage.setHeader(CONVERSATION_ID, conversation.getId());
208                                                
209                                    amf3ResponseMessage.setHeader(IS_LONG_RUNNING_CONVERSATION, !conversation.isTransient());
210                                        }
211                                }
212                                finally {
213                    if (conversationManager != null)
214                                            conversationManager.destroyConversation(beanManager);
215                                        
216                                        HttpGraniteContext httpContext = ((HttpGraniteContext)context);
217
218                    if (requestListener != null) {
219                        // Destroy the CDI context
220                        HttpServletRequestParamWrapper requestWrapper = (HttpServletRequestParamWrapper)httpContext.getRequest().getAttribute(REQUESTWRAPPER_ATTR);
221                        httpContext.getRequest().removeAttribute(REQUESTWRAPPER_ATTR);
222                        ServletRequestEvent event = new ServletRequestEvent(httpContext.getServletContext(), requestWrapper);
223                        requestListener.requestDestroyed(event);
224
225                        log.debug("Destroying wrapped CDI AMF request");
226
227                        Integer wrapCount = (Integer)httpContext.getRequest().getAttribute(MESSAGECOUNT_ATTR);
228                        if (wrapCount == 1) {
229                            log.debug("Restoring default container request context");
230                            event = new ServletRequestEvent(((HttpGraniteContext)context).getServletContext(), httpContext.getRequest());
231                            requestListener.requestInitialized(event);
232                            httpContext.getRequest().removeAttribute(MESSAGECOUNT_ATTR);
233                        }
234                        else
235                            httpContext.getRequest().setAttribute(MESSAGECOUNT_ATTR, wrapCount-1);
236                    }
237                                }
238                        }
239                }
240                catch (Exception e) {
241            log.error(e, "Exception while post processing the response message.");
242            throw new ServiceException("Error while post processing the response message - " + e.getMessage());
243                }
244        }
245}