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.gravity.tomcat;
022    
023    import java.io.IOException;
024    import java.io.InputStream;
025    
026    import javax.servlet.ServletConfig;
027    import javax.servlet.ServletException;
028    import javax.servlet.http.HttpServletRequest;
029    import javax.servlet.http.HttpServletResponse;
030    
031    import org.apache.catalina.CometEvent;
032    import org.apache.catalina.CometProcessor;
033    import org.granite.gravity.AbstractGravityServlet;
034    import org.granite.logging.Logger;
035    
036    /**
037     * @author Franck WOLFF
038     */
039    public abstract class AbstractCometProcessor extends AbstractGravityServlet implements CometProcessor {
040    
041        ///////////////////////////////////////////////////////////////////////////
042        // Fields.
043    
044        private static final long serialVersionUID = 1L;
045        private static final Logger log = Logger.getLogger(AbstractCometProcessor.class);
046        
047        private boolean longPollingTimeoutSupported = true;
048    
049        ///////////////////////////////////////////////////////////////////////////
050        // Initialization.
051    
052        @Override
053        public void init(ServletConfig config) throws ServletException {
054            super.init(config, new TomcatChannelFactory());
055        }
056    
057        ///////////////////////////////////////////////////////////////////////////
058        // Abstract methods.
059    
060            public abstract CometIO createCometIO();
061            
062        public abstract boolean handleRequest(CometEvent event, InputStream content)
063            throws IOException, ServletException;
064    
065        public abstract boolean handleEnd(CometEvent event)
066            throws IOException, ServletException;
067    
068        public abstract boolean handleError(CometEvent event)
069            throws IOException, ServletException;
070    
071        ///////////////////////////////////////////////////////////////////////////
072        // CometProcessor implementation.
073    
074        public void event(CometEvent event) throws IOException, ServletException {
075            
076            // make sure we've got a valid CometEvent (should never happen)
077            if (!EventUtil.isValid(event)) {
078                    log.error("Tomcat sent an invalid CometEvent: %s.%s", event.getEventType(), event.getEventSubType());
079                    return;
080            }
081    
082            if (log.isDebugEnabled()) {
083                    log.debug(
084                        "%s.%s: %s/%s",
085                        event.getEventType(), event.getEventSubType(),
086                        event.getHttpServletRequest(), event.getHttpServletResponse()
087                    );
088            }
089    
090            if (event.getEventType() == CometEvent.EventType.BEGIN)
091                begin(event);
092            else if (event.getEventType() == CometEvent.EventType.READ)
093                read(event);
094            else if (event.getEventType() == CometEvent.EventType.END)
095                end(event);
096            else if (event.getEventType() == CometEvent.EventType.ERROR)
097                error(event);
098            else
099                throw new ServletException("Unknown CometEvent type: " + event.getEventType());
100        }
101    
102        ///////////////////////////////////////////////////////////////////////////
103        // Comet events processing.
104    
105        protected void begin(CometEvent event) throws IOException, ServletException {
106            boolean close = true;
107            try {
108                    // Event timeout isn't supported with APR connectors...
109                    if (longPollingTimeoutSupported) {
110                            try {
111                                    event.setTimeout((int)getLongPollingTimeout());
112                            }
113                            catch (Exception e) {
114                                    longPollingTimeoutSupported = false;
115                            }
116                    }
117    
118                    HttpServletRequest request = event.getHttpServletRequest();
119                CometIO io = createCometIO();
120                    io.readFully(request.getInputStream());
121                    request.getParameter("");       // Patch GDS-495
122                    
123                    close = handleRequest(event, io.getInputStream());
124            }
125            finally {
126                    if (close) {
127                    try {
128                            event.close();
129                    } catch (Exception e) {
130                            log.debug(e, "Could not close event: %s", EventUtil.toString(event));
131                    }
132                    }
133            }
134        }
135    
136            protected void read(CometEvent event) {
137                    // This implementation doesn't use asynchronous reads.
138                    throw new RuntimeException("Unsupported operation");
139        }
140        
141            protected void end(CometEvent event) throws IOException, ServletException {
142                    boolean close = true;
143                    try {
144                            close = handleEnd(event);
145                    }
146                    finally {
147                            if (close) {
148                            try {
149                                    event.close();
150                            } catch (Exception e) {
151                                    log.debug(e, "Could not close event: %s", EventUtil.toString(event));
152                            }
153                            }
154            }
155        }
156        
157        protected void error(CometEvent event) throws IOException, ServletException {
158            boolean close = true;
159            try {
160                    close = handleError(event);
161            }
162            finally {
163                    if (close) {
164                            try {
165                                    event.close();
166                            } catch (Exception e) {
167                                    log.debug(e, "Could not close event: %s", EventUtil.toString(event));
168                            }
169                    }
170            }
171        }
172    
173        ///////////////////////////////////////////////////////////////////////////
174        // Utility.
175    
176        @Override
177        protected void service(HttpServletRequest request, HttpServletResponse response)
178            throws IOException, ServletException {
179            throw new ServletException("Not in a valid Comet configuration (use an APR or NIO connector)");
180        }
181    }