[#859]:Allow SseEventSink.close() to throw IOException (#863)
diff --git a/examples/src/main/java/jaxrs/examples/sse/ServerSentEventsResource.java b/examples/src/main/java/jaxrs/examples/sse/ServerSentEventsResource.java index 667e27e..dc19a68 100644 --- a/examples/src/main/java/jaxrs/examples/sse/ServerSentEventsResource.java +++ b/examples/src/main/java/jaxrs/examples/sse/ServerSentEventsResource.java
@@ -98,7 +98,10 @@ sseEventSink.close(); } catch (final InterruptedException e) { e.printStackTrace(); + } catch (IOException ioe) { + //handle I/O error } + }); } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java b/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java index bf37ce3..b245435 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java
@@ -16,6 +16,7 @@ package jakarta.ws.rs.sse; +import java.io.IOException; import java.util.concurrent.CompletionStage; /** @@ -71,7 +72,8 @@ * <p> * Subsequent calls have no effect and are ignored. Once the {@link SseEventSink} is closed, invoking any method other * than this one and {@link #isClosed()} would result in an {@link IllegalStateException} being thrown. + * @throws IOException if an I/O error occurs. */ @Override - void close(); + void close() throws IOException; }
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/CloseResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/CloseResource.java index 4fd3907..1aa1ad5 100644 --- a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/CloseResource.java +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/CloseResource.java
@@ -25,6 +25,9 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.sse.Sse; import jakarta.ws.rs.sse.SseEventSink; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; @Path("close") public class CloseResource { @@ -33,6 +36,8 @@ private static volatile boolean isClosed = false; + private static final Logger LOG = Logger.getLogger(CloseResource.class.getName()); + @GET @Path("reset") @Produces(MediaType.SERVER_SENT_EVENTS) @@ -41,6 +46,8 @@ isClosed = false; try (SseEventSink s = sink) { s.send(sse.newEvent("RESET")); + } catch (IOException e) { + throw new RuntimeException(e); } } @@ -52,15 +59,19 @@ public void run() { SseEventSink s = sink; s.send(sse.newEvent(SSEMessage.MESSAGE)); - s.close(); - isClosed = s.isClosed(); - if (!isClosed) - return; - s.close(); - isClosed = s.isClosed(); - if (!isClosed) - return; - s.close(); + try { + s.close(); + isClosed = s.isClosed(); + if (!isClosed) + return; + s.close(); + isClosed = s.isClosed(); + if (!isClosed) + return; + s.close(); + } catch (IOException e) { + //ignore this exception and isClosed will be checked later. + } isClosed = s.isClosed(); if (!isClosed) return; @@ -88,6 +99,8 @@ return; } s.send(sse.newEvent("CHECK")); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } }
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/MBWCheckResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/MBWCheckResource.java index c2ef47d..5eca3f8 100644 --- a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/MBWCheckResource.java +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/MBWCheckResource.java
@@ -16,6 +16,7 @@ package ee.jakarta.tck.ws.rs.jaxrs21.ee.sse.sseeventsink; +import ee.jakarta.tck.ws.rs.jaxrs21.ee.sse.sseeventsource.ServiceUnavailableResource; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -23,6 +24,8 @@ import java.nio.file.Files; import java.nio.file.StandardOpenOption; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.xml.namespace.QName; import ee.jakarta.tck.ws.rs.common.impl.SinglevaluedMap; @@ -46,7 +49,7 @@ @Path("mbw") public class MBWCheckResource { static final String MESSAGE = SSEMessage.MESSAGE; - + private static final Logger LOG = Logger.getLogger(MBWCheckResource.class.getName()); @GET @Path("boolean") @Produces(MediaType.SERVER_SENT_EVENTS) @@ -54,6 +57,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(true) .mediaType(MediaType.TEXT_PLAIN_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -64,6 +69,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(MESSAGE.getBytes()) .mediaType(MediaType.WILDCARD_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -74,6 +81,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(MESSAGE.charAt(0)) .mediaType(MediaType.TEXT_PLAIN_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -85,6 +94,8 @@ s.send(sse.newEventBuilder() .data(new StringDataSource(MESSAGE, MediaType.TEXT_PLAIN_TYPE)) .mediaType(MediaType.WILDCARD_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -95,6 +106,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(Double.MAX_VALUE) .mediaType(MediaType.TEXT_PLAIN_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -115,6 +128,8 @@ s.send(sse.newEvent(e.getMessage())); throw new RuntimeException(e); // log to server log } + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -126,6 +141,8 @@ s.send(sse.newEventBuilder() .data(new ByteArrayInputStream(MESSAGE.getBytes())) .mediaType(MediaType.WILDCARD_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -136,6 +153,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(Integer.MIN_VALUE) .mediaType(MediaType.TEXT_PLAIN_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -148,6 +167,8 @@ String.class, MESSAGE); s.send(sse.newEventBuilder().data(element) .mediaType(MediaType.APPLICATION_XML_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -160,6 +181,8 @@ map.add("name", MESSAGE); s.send(sse.newEventBuilder().data(map) .mediaType(MediaType.APPLICATION_FORM_URLENCODED_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -172,6 +195,8 @@ .data(new InputStreamReader( new ByteArrayInputStream(MESSAGE.getBytes()))) .mediaType(MediaType.WILDCARD_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -184,6 +209,8 @@ StringStreamingOutput output = new StringStreamingOutput(MESSAGE); s.send(sse.newEventBuilder().data(output) .mediaType(MediaType.WILDCARD_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -194,6 +221,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(MESSAGE) .mediaType(MediaType.WILDCARD_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -205,6 +234,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(new StringSource(MESSAGE)) .mediaType(MediaType.TEXT_XML_TYPE).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } }
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/StageCheckerResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/StageCheckerResource.java index 93d4e2a..a40a3c5 100644 --- a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/StageCheckerResource.java +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsink/StageCheckerResource.java
@@ -18,6 +18,7 @@ import static ee.jakarta.tck.ws.rs.jaxrs21.ee.sse.SSEJAXRSClient.MESSAGE; +import java.io.IOException; import java.util.concurrent.CompletableFuture; import jakarta.ws.rs.GET; @@ -34,7 +35,7 @@ @GET @Produces(MediaType.SERVER_SENT_EVENTS) - public void send(@Context SseEventSink sink, @Context Sse sse) { + public void send(@Context SseEventSink sink, @Context Sse sse) throws IOException{ try (SseEventSink s = sink) { CompletableFuture<?> stage = s.send(sse.newEvent(MESSAGE)) .toCompletableFuture(); @@ -48,6 +49,8 @@ } } s.send(sse.newEvent(DONE)); + } catch (IOException e) { + e.printStackTrace(); } } }
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/MediaTypeResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/MediaTypeResource.java index e50502a..982a94b 100644 --- a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/MediaTypeResource.java +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/MediaTypeResource.java
@@ -16,6 +16,9 @@ package ee.jakarta.tck.ws.rs.jaxrs21.ee.sse.sseeventsource; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.xml.namespace.QName; import ee.jakarta.tck.ws.rs.common.impl.JaxbKeyValueBean; @@ -31,11 +34,11 @@ import jakarta.ws.rs.sse.Sse; import jakarta.ws.rs.sse.SseEventSink; import jakarta.xml.bind.JAXBElement; - +import java.util.logging.Logger; @Path("media") public class MediaTypeResource { private static MediaType mediaType = MediaType.WILDCARD_TYPE; - + private static final Logger LOG = Logger.getLogger(MediaTypeResource.class.getName()); @POST @Path("set") public String setMediaType(String media) { @@ -51,6 +54,8 @@ try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(SSEMessage.MESSAGE).mediaType(mediaType) .build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -62,6 +67,8 @@ JAXBElement<String> element = new JAXBElement<String>(new QName("name"), String.class, SSEMessage.MESSAGE); s.send(sse.newEventBuilder().data(element).mediaType(mediaType).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -73,6 +80,8 @@ JaxbKeyValueBean bean = new JaxbKeyValueBean(); bean.set("key", SSEMessage.MESSAGE); s.send(sse.newEventBuilder().data(bean).mediaType(mediaType).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -84,6 +93,8 @@ SinglevaluedMap<String, String> map = new SinglevaluedMap<>(); map.add("key", SSEMessage.MESSAGE); s.send(sse.newEventBuilder().data(map).mediaType(mediaType).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } }
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/ServiceUnavailableResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/ServiceUnavailableResource.java index 025854a..55aba35 100644 --- a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/ServiceUnavailableResource.java +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs21/ee/sse/sseeventsource/ServiceUnavailableResource.java
@@ -31,6 +31,9 @@ import jakarta.ws.rs.sse.OutboundSseEvent; import jakarta.ws.rs.sse.Sse; import jakarta.ws.rs.sse.SseEventSink; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; @Path("su") public class ServiceUnavailableResource { @@ -44,6 +47,8 @@ static final String MESSAGE = SSEMessage.MESSAGE; + private static final Logger LOG = Logger.getLogger(ServiceUnavailableResource.class.getName()); + @GET @Path("reset") public String reset() { @@ -105,6 +110,8 @@ } else { try (SseEventSink s = sink) { s.send(sse.newEvent(MESSAGE)); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } } @@ -118,7 +125,11 @@ count++; if (isConnectionLost != 0) { isConnectionLost--; - sink.close(); + try { + sink.close(); + } catch (IOException e) { + e.printStackTrace(); + } /* * To cancel a stream from the server, respond with a non * "text/event-stream" Content-Type or return an HTTP status other than @@ -127,6 +138,8 @@ } else { try (SseEventSink s = sink) { s.send(sse.newEvent(MESSAGE)); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } } @@ -138,6 +151,8 @@ public void sendRetry(@Context SseEventSink sink, @Context Sse sse) { try (SseEventSink s = sink) { s.send(sse.newEventBuilder().data(MESSAGE).reconnectDelay(3000L).build()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } @@ -148,6 +163,8 @@ try (SseEventSink s = sink) { s.send( (OutboundSseEvent) new OutboundSSEEventImpl(MESSAGE).setDelay(20000)); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to close SseEventSink", e); } } }