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