Bug 515912 - IllegalStateException when using WriteListener

- delegate all write() calls to original output stream

Change-Id: Ia630af93bc1e8e3a97bc4a876c8407516ba6d32f
Signed-off-by: Peter Nehrer <pnehrer@eclipticalsoftware.com>
diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java
index fd22600..2a422b0 100644
--- a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java
+++ b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java
@@ -9,6 +9,7 @@
  *     IBM Corporation - initial API and implementation
  *     Raymond Augé - bug fixes and enhancements
  *     Juan Gonzalez <juan.gonzalez@liferay.com> - Bug 486412
+ *     Peter Nehrer <pnehrer@eclipticalsoftware.com> - Bug 515912
  *******************************************************************************/
 package org.eclipse.equinox.http.servlet.tests;
 
@@ -21,7 +22,6 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-
 import java.net.CookieHandler;
 import java.net.CookieManager;
 import java.net.CookiePolicy;
@@ -79,6 +79,7 @@
 import org.eclipse.equinox.http.servlet.context.ContextPathCustomizer;
 import org.eclipse.equinox.http.servlet.session.HttpSessionInvalidator;
 import org.eclipse.equinox.http.servlet.testbase.BaseTest;
+import org.eclipse.equinox.http.servlet.tests.util.AsyncOutputServlet;
 import org.eclipse.equinox.http.servlet.tests.util.BaseAsyncServlet;
 import org.eclipse.equinox.http.servlet.tests.util.BaseChangeSessionIdServlet;
 import org.eclipse.equinox.http.servlet.tests.util.BaseHttpContext;
@@ -3550,6 +3551,7 @@
 		Assert.assertEquals(0, listenerBalance.get());
 	}
 
+	@Test
 	public void test_Async1() throws Exception {
 
 		Servlet s1 = new BaseAsyncServlet("test_Listener8");
@@ -3573,6 +3575,31 @@
 	}
 
 	@Test
+	public void test_AsyncOutput1() throws Exception {
+		Servlet s1 = new AsyncOutputServlet();
+		Collection<ServiceRegistration<?>> registrations = new ArrayList<ServiceRegistration<?>>();
+		try {
+			Dictionary<String, Object> servletProps1 = new Hashtable<String, Object>();
+			servletProps1.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME, "AsyncOutputServlet");
+			servletProps1.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/asyncOutput");
+			servletProps1.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_ASYNC_SUPPORTED, true);
+			registrations.add(getBundleContext().registerService(Servlet.class, s1, servletProps1));
+
+			String output1 = requestAdvisor.request("asyncOutput");
+
+			Assert.assertTrue("write(int)", output1.startsWith("0123456789"));
+
+			String output2 = requestAdvisor.request("asyncOutput?bytes=true");
+
+			Assert.assertTrue("write(byte[], int, int)", output2.startsWith("0123456789"));
+		} finally {
+			for (ServiceRegistration<?> registration : registrations) {
+				registration.unregister();
+			}
+		}
+	}
+
+	@Test
 	public void test_WBServlet1() throws Exception {
 		String expected = "a";
 		String actual;
diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/util/AsyncOutputServlet.java b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/util/AsyncOutputServlet.java
new file mode 100644
index 0000000..7f1d71a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/util/AsyncOutputServlet.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Ecliptical Software Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Ecliptical Software Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.http.servlet.tests.util;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class AsyncOutputServlet extends HttpServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+		resp.setStatus(HttpServletResponse.SC_OK);
+		resp.setContentType("text/plain");
+		resp.setBufferSize(16);
+		resp.flushBuffer();
+		AsyncContext async = req.startAsync(req, resp);
+		ServletOutputStream out = resp.getOutputStream();
+		out.setWriteListener(new AsyncWriter(async, Boolean.parseBoolean(req.getParameter("bytes"))));
+	}
+
+	private class AsyncWriter implements WriteListener {
+
+		private final AsyncContext async;
+
+		private final boolean writeBytes;
+
+		private boolean eof;
+
+		public AsyncWriter(AsyncContext async, boolean writeBytes) {
+			this.async = async;
+			this.writeBytes = writeBytes;
+		}
+
+		@Override
+		public void onWritePossible() throws IOException {
+			HttpServletResponse resp = (HttpServletResponse) async.getResponse();
+			ServletOutputStream out = resp.getOutputStream();
+			if (eof) {
+				out.close();
+				async.complete();
+				return;
+			}
+
+			if (writeBytes) {
+				byte[] buf = new byte[10];
+				for (int i = 0; i < buf.length; ++i) {
+					buf[i] = (byte) ('0' + i);
+				}
+
+				do {
+					out.write(buf);
+				} while (out.isReady());
+			} else {
+				int i = -1;
+				do {
+					out.write('0' + (++i % 10));
+				} while (out.isReady());
+			}
+
+			eof = true;
+		}
+
+		@Override
+		public void onError(Throwable t) {
+			try {
+				async.complete();
+			} finally {
+				getServletContext().log("Error writing response.", t);
+			}
+		}
+	}
+}
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletResponseWrapperImpl.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletResponseWrapperImpl.java
index a7c5f5f..db9841e 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletResponseWrapperImpl.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletResponseWrapperImpl.java
@@ -126,6 +126,22 @@
 			originalOutputStream.write(b);
 		}
 
+		@Override
+		public void write(byte[] b) throws IOException {
+			if (isCompleted()) {
+				return;
+			}
+			originalOutputStream.write(b);
+		}
+
+		@Override
+		public void write(byte[] b, int off, int len) throws IOException {
+			if (isCompleted()) {
+				return;
+			}
+			originalOutputStream.write(b, off, len);
+		}
+
 		private final ServletOutputStream originalOutputStream;
 
 	}