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    
021    package org.granite.messaging.webapp;
022    
023    import java.io.BufferedInputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.OutputStream;
027    
028    import javax.servlet.Filter;
029    import javax.servlet.FilterChain;
030    import javax.servlet.FilterConfig;
031    import javax.servlet.ServletException;
032    import javax.servlet.ServletRequest;
033    import javax.servlet.ServletResponse;
034    import javax.servlet.http.HttpServletRequest;
035    import javax.servlet.http.HttpServletResponse;
036    
037    import org.granite.config.GraniteConfig;
038    import org.granite.config.ServletGraniteConfig;
039    import org.granite.config.flex.ServicesConfig;
040    import org.granite.config.flex.ServletServicesConfig;
041    import org.granite.context.AMFContextImpl;
042    import org.granite.context.GraniteContext;
043    import org.granite.logging.Logger;
044    import org.granite.messaging.amf.AMF0Message;
045    import org.granite.messaging.amf.io.AMF0Deserializer;
046    import org.granite.messaging.amf.io.AMF0Serializer;
047    import org.granite.messaging.jmf.JMFDeserializer;
048    import org.granite.messaging.jmf.JMFSerializer;
049    import org.granite.messaging.jmf.JMFServletContextListener;
050    import org.granite.messaging.jmf.SharedContext;
051    import org.granite.util.ContentType;
052    import org.granite.util.ServletParams;
053    
054    /**
055     * @author Franck WOLFF
056     */
057    public 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    }