Merge pull request #5733 from eclipse-ee4j/2.45-BRANCH
Technical merge of released branch 2.45-BRANCH
diff --git a/core-common/src/main/java/org/glassfish/jersey/io/package-info.java b/core-common/src/main/java/org/glassfish/jersey/io/package-info.java
new file mode 100644
index 0000000..f913ae6
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/io/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Common Jersey core io classes.
+ */
+package org.glassfish.jersey.io;
diff --git a/core-common/src/main/java/org/glassfish/jersey/io/spi/FlushedCloseable.java b/core-common/src/main/java/org/glassfish/jersey/io/spi/FlushedCloseable.java
new file mode 100644
index 0000000..12aa714
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/io/spi/FlushedCloseable.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.io.spi;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A marker interface that the stream provided to Jersey can implement,
+ * noting that the stream does not need to call {@link #flush()} prior to {@link #close()}.
+ * That way, {@link #flush()} method is not called twice.
+ *
+ * <p>
+ * Usable by {@link javax.ws.rs.client.ClientRequestContext#setEntityStream(OutputStream)}.
+ * Usable by {@link javax.ws.rs.container.ContainerResponseContext#setEntityStream(OutputStream)}.
+ * </p>
+ *
+ * <p>
+ * This marker interface can be useful for the customer OutputStream to know the {@code flush} did not come from
+ * Jersey before close. By default, when the entity stream is to be closed by Jersey, {@code flush} is called first.
+ * </p>
+ */
+public interface FlushedCloseable extends Flushable, Closeable {
+ /**
+ * Flushes this stream by writing any buffered output to the underlying stream.
+ * Then closes this stream and releases any system resources associated
+ * with it. If the stream is already closed then invoking this
+ * method has no effect.
+ *
+ * <p> As noted in {@link AutoCloseable#close()}, cases where the
+ * close may fail require careful attention. It is strongly advised
+ * to relinquish the underlying resources and to internally
+ * <em>mark</em> the {@code Closeable} as closed, prior to throwing
+ * the {@code IOException}.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public void close() throws IOException;
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/io/spi/package-info.java b/core-common/src/main/java/org/glassfish/jersey/io/spi/package-info.java
new file mode 100644
index 0000000..7a70945
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/io/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Common Jersey core io SPI classes.
+ */
+package org.glassfish.jersey.io.spi;
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java
index dd5816f..3265a96 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java
@@ -20,43 +20,33 @@
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
-import java.net.URI;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.Configuration;
-import javax.ws.rs.core.Cookie;
-import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.NewCookie;
-import javax.ws.rs.ext.RuntimeDelegate;
import org.glassfish.jersey.CommonProperties;
import org.glassfish.jersey.internal.RuntimeDelegateDecorator;
-import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.GuardianStringKeyMultivaluedMap;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
+import org.glassfish.jersey.io.spi.FlushedCloseable;
/**
* Base outbound message context implementation.
@@ -572,11 +562,13 @@
if (hasEntity()) {
try {
final OutputStream es = getEntityStream();
- es.flush();
+ if (!FlushedCloseable.class.isInstance(es)) {
+ es.flush();
+ }
es.close();
} catch (IOException e) {
// Happens when the client closed connection before receiving the full response.
- // This is OK and not interesting in vast majority of the cases
+ // This is OK and not interesting in the vast majority of the cases
// hence the log level set to FINE to make sure it does not flood the log unnecessarily
// (especially for clients disconnecting from SSE listening, which is very common).
Logger.getLogger(OutboundMessageContext.class.getName()).log(Level.FINE, e.getMessage(), e);
diff --git a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
index 1327edd..a909dfa 100644
--- a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
+++ b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,6 +16,9 @@
package org.glassfish.jersey.tests.e2e.common.message.internal;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
@@ -33,10 +36,13 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.RuntimeDelegate;
+import org.glassfish.jersey.io.spi.FlushedCloseable;
import org.glassfish.jersey.message.internal.CookieProvider;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
import org.glassfish.jersey.tests.e2e.common.TestRuntimeDelegate;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.contains;
@@ -271,4 +277,38 @@
newCtx.setMediaType(MediaType.APPLICATION_XML_TYPE); // new value
Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, newCtx.getMediaType());
}
+
+ @Test
+ public void OutboundMessageContextFlushTest() throws IOException {
+ FlushCountOutputStream os = new FlushCountOutputStream();
+ OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
+ ctx.setEntity("Anything");
+ ctx.setEntityStream(os);
+ os.flush();
+ ctx.close();
+ MatcherAssert.assertThat(os.flushedCnt, Matchers.is(2));
+
+ os = new FlushedClosableOutputStream();
+ ctx = new OutboundMessageContext((Configuration) null);
+ ctx.setEntity("Anything2");
+ ctx.setEntityStream(os);
+ os.flush();
+ ctx.close();
+ MatcherAssert.assertThat(os.flushedCnt, Matchers.is(1));
+ }
+
+ private static class FlushCountOutputStream extends ByteArrayOutputStream {
+ private int flushedCnt = 0;
+
+ @Override
+ public void flush() throws IOException {
+ flushedCnt++;
+ super.flush();
+ }
+ }
+
+ private static class FlushedClosableOutputStream extends FlushCountOutputStream implements FlushedCloseable {
+
+ }
}
+