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