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