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}