001/*
002  GRANITE DATA SERVICES
003  Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004
005  This file is part of Granite Data Services.
006
007  Granite Data Services is free software; you can redistribute it and/or modify
008  it under the terms of the GNU Library General Public License as published by
009  the Free Software Foundation; either version 2 of the License, or (at your
010  option) any later version.
011
012  Granite Data Services is distributed in the hope that it will be useful, but
013  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015  for more details.
016
017  You should have received a copy of the GNU Library General Public License
018  along with this library; if not, see <http://www.gnu.org/licenses/>.
019*/
020
021package org.granite.messaging.webapp;
022
023import java.io.BufferedInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.OutputStream;
027
028import javax.servlet.Filter;
029import javax.servlet.FilterChain;
030import javax.servlet.FilterConfig;
031import javax.servlet.ServletException;
032import javax.servlet.ServletRequest;
033import javax.servlet.ServletResponse;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpServletResponse;
036
037import org.granite.config.GraniteConfig;
038import org.granite.config.ServletGraniteConfig;
039import org.granite.config.flex.ServicesConfig;
040import org.granite.config.flex.ServletServicesConfig;
041import org.granite.context.AMFContextImpl;
042import org.granite.context.GraniteContext;
043import org.granite.logging.Logger;
044import org.granite.messaging.amf.AMF0Message;
045import org.granite.messaging.amf.io.AMF0Deserializer;
046import org.granite.messaging.amf.io.AMF0Serializer;
047import org.granite.messaging.jmf.JMFDeserializer;
048import org.granite.messaging.jmf.JMFSerializer;
049import org.granite.messaging.jmf.JMFServletContextListener;
050import org.granite.messaging.jmf.SharedContext;
051import org.granite.util.ContentType;
052import org.granite.util.ServletParams;
053
054/**
055 * @author Franck WOLFF
056 */
057public class AMFMessageFilter implements Filter {
058
059    private static final Logger log = Logger.getLogger(AMFMessageFilter.class);
060    
061    protected FilterConfig config = null;
062    protected GraniteConfig graniteConfig = null;
063    protected ServicesConfig servicesConfig = null;
064    
065    protected Integer inputBufferSize = null;
066    protected Integer outputBufferSize = null;
067    protected boolean closeStreams = true;
068    
069    protected SharedContext jmfSharedContext = null;
070
071    public void init(FilterConfig config) throws ServletException {
072        this.config = config;
073        this.graniteConfig = ServletGraniteConfig.loadConfig(config.getServletContext());
074        this.servicesConfig = ServletServicesConfig.loadConfig(config.getServletContext());
075        
076        closeStreams = ServletParams.get(config, "closeStreams", Boolean.TYPE, true);
077        inputBufferSize = ServletParams.get(config, "inputBufferSize", Integer.TYPE, null);
078        outputBufferSize = ServletParams.get(config, "outputBufferSize", Integer.TYPE, null);
079        
080        if (inputBufferSize != null && inputBufferSize <= 0)
081                throw new ServletException("Illegal value for inputBufferSize=" + inputBufferSize + " (should be > 0, fix your web.xml)");
082        if (outputBufferSize != null && outputBufferSize <= 0)
083                throw new ServletException("Illegal value for outputBufferSize=" + outputBufferSize + " (should be > 0, fix your web.xml)");
084        
085        log.info("Using configuration: {closeStreams=%s, inputBufferSize=%s, outputBufferSize=%s}", closeStreams, inputBufferSize, outputBufferSize);
086        
087        jmfSharedContext = JMFServletContextListener.getSharedContext(config.getServletContext());
088    }
089
090    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
091        throws IOException, ServletException {
092
093        if (!(req instanceof HttpServletRequest) || !(resp instanceof HttpServletResponse))
094            throw new ServletException("Not in HTTP context: " + req + ", " + resp);
095
096        HttpServletRequest request = (HttpServletRequest)req;
097        HttpServletResponse response = (HttpServletResponse)resp;
098        
099        if (ContentType.JMF_AMF.mimeType().equals(request.getContentType()))
100                doJMFAMFFilter(request, response, chain);
101        else
102                doAMFFilter(request, response, chain);
103    }
104    
105    protected void doAMFFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
106        throws IOException, ServletException {
107        
108        log.debug(">> Incoming AMF0 request from: %s", request.getRequestURL());
109
110        InputStream is = null;
111        OutputStream os = null;
112        
113        try {
114                if (inputBufferSize != null)
115                        is = new BufferedInputStream(request.getInputStream(), inputBufferSize);
116                else
117                        is = request.getInputStream();
118                
119            GraniteContext context = HttpGraniteContext.createThreadIntance(
120                graniteConfig, servicesConfig, config.getServletContext(),
121                request, response
122            );
123
124            AMFContextImpl amf = (AMFContextImpl)context.getAMFContext();
125
126            log.debug(">> Deserializing AMF0 request...");
127
128            AMF0Deserializer deserializer = new AMF0Deserializer(is);
129            AMF0Message amf0Request = deserializer.getAMFMessage();
130
131            amf.setAmf0Request(amf0Request);
132
133            log.debug(">> Chaining AMF0 request: %s", amf0Request);
134
135            chain.doFilter(request, response);
136
137            AMF0Message amf0Response = amf.getAmf0Response();
138
139            log.debug("<< Serializing AMF0 response: %s", amf0Response);
140
141            response.setStatus(HttpServletResponse.SC_OK);
142            response.setContentType(ContentType.AMF.mimeType());
143                response.setDateHeader("Expire", 0L);
144                response.setHeader("Cache-Control", "no-store");
145            
146                if (outputBufferSize != null)
147                        response.setBufferSize(outputBufferSize);
148            
149                os = response.getOutputStream();
150            AMF0Serializer serializer = new AMF0Serializer(os);
151            
152            serializer.serializeMessage(amf0Response);
153            
154            response.flushBuffer();
155        }
156        catch (IOException e) {
157                if ("org.apache.catalina.connector.ClientAbortException".equals(e.getClass().getName()))
158                        log.debug(e, "Connection closed by client");
159                else
160                        log.error(e, "AMF message error");
161            throw e;
162        }
163        catch (Exception e) {
164            log.error(e, "AMF message error");
165            throw new ServletException(e);
166        }
167        finally {
168                
169                if (closeStreams) {
170                        if (is != null) {
171                                try {
172                                        is.close();
173                                } catch (IOException e) {
174                                        log.error(e, "Error while closing request input stream");
175                                }
176                        }
177                        
178                        if (os != null) {
179                                try {
180                                        os.close();
181                                } catch (IOException e) {
182                                        log.error(e, "Error while closing response output stream");
183                                }
184                        }
185                }
186                
187            GraniteContext.release();
188        }
189    }
190    
191    protected void doJMFAMFFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
192        throws IOException, ServletException {
193        
194        log.debug(">> Incoming JMF+AMF request from: %s", request.getRequestURL());
195        
196        if (jmfSharedContext == null)
197                throw JMFServletContextListener.newSharedContextNotInitializedException();
198
199        InputStream is = null;
200        OutputStream os = null;
201        
202        try {
203                is = request.getInputStream();
204                
205            GraniteContext context = HttpGraniteContext.createThreadIntance(
206                graniteConfig, servicesConfig, config.getServletContext(),
207                request, response
208            );
209
210            AMFContextImpl amf = (AMFContextImpl)context.getAMFContext();
211
212            log.debug(">> Deserializing JMF+AMF request...");
213
214            @SuppressWarnings("all") // JDK7 warning (Resource leak: 'deserializer' is never closed)...
215                        JMFDeserializer deserializer = new JMFDeserializer(is, jmfSharedContext);
216            AMF0Message amf0Request = (AMF0Message)deserializer.readObject();
217
218            amf.setAmf0Request(amf0Request);
219
220            log.debug(">> Chaining AMF0 request: %s", amf0Request);
221
222            chain.doFilter(request, response);
223
224            AMF0Message amf0Response = amf.getAmf0Response();
225
226            log.debug("<< Serializing JMF+AMF response: %s", amf0Response);
227
228            response.setStatus(HttpServletResponse.SC_OK);
229            response.setContentType(ContentType.JMF_AMF.mimeType());
230                response.setDateHeader("Expire", 0L);
231                response.setHeader("Cache-Control", "no-store");
232            
233                os = response.getOutputStream();
234
235            @SuppressWarnings("all") // JDK7 warning (Resource leak: 'serializer' is never closed)...
236                JMFSerializer serializer = new JMFSerializer(os, jmfSharedContext);
237            serializer.writeObject(amf0Response);
238            
239            response.flushBuffer();
240        }
241        catch (IOException e) {
242                if ("org.apache.catalina.connector.ClientAbortException".equals(e.getClass().getName()))
243                        log.debug(e, "Connection closed by client");
244                else
245                        log.error(e, "JMF+AMF message error");
246            throw e;
247        }
248        catch (Exception e) {
249            log.error(e, "JMF+AMF message error");
250            throw new ServletException(e);
251        }
252        finally {
253                if (is != null) {
254                        try {
255                                is.close();
256                        } catch (IOException e) {
257                                log.error(e, "Error while closing request input stream");
258                        }
259                }
260                
261                if (os != null) {
262                        try {
263                                os.close();
264                        } catch (IOException e) {
265                                log.error(e, "Error while closing response output stream");
266                        }
267                }
268                
269            GraniteContext.release();
270        }
271    }
272
273    public void destroy() {
274        this.config = null;
275        this.graniteConfig = null;
276        this.servicesConfig = null;
277        
278        this.jmfSharedContext = null;
279    }
280}