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