1+ /*
2+ * Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
3+ *
4+ * This program and the accompanying materials are made available under the
5+ * terms of the Eclipse Public License v. 2.0, which is available at
6+ * http://www.eclipse.org/legal/epl-2.0.
7+ *
8+ * This Source Code may also be made available under the following Secondary
9+ * Licenses when the conditions for such availability set forth in the
10+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+ * version 2 with the GNU Classpath Exception, which is available at
12+ * https://www.gnu.org/software/classpath/license.html.
13+ *
14+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+ */
16+
17+ package org .glassfish .jersey .server ;
18+
19+
20+ import jakarta .ws .rs .GET ;
21+ import jakarta .ws .rs .Path ;
22+ import jakarta .ws .rs .Produces ;
23+ import jakarta .ws .rs .core .MediaType ;
24+ import jakarta .ws .rs .core .Response ;
25+ import jakarta .ws .rs .core .StreamingOutput ;
26+ import org .glassfish .jersey .internal .MapPropertiesDelegate ;
27+ import org .glassfish .jersey .io .spi .FlushedCloseable ;
28+ import org .glassfish .jersey .message .MessageBodyWorkers ;
29+ import org .glassfish .jersey .server .RequestContextBuilder .TestContainerRequest ;
30+ import org .glassfish .jersey .server .spi .ContainerResponseWriter ;
31+ import org .hamcrest .MatcherAssert ;
32+ import org .hamcrest .Matchers ;
33+ import org .junit .jupiter .api .Test ;
34+
35+ import java .io .ByteArrayOutputStream ;
36+ import java .io .IOException ;
37+ import java .io .OutputStream ;
38+ import java .net .URI ;
39+ import java .nio .charset .StandardCharsets ;
40+ import java .util .concurrent .Future ;
41+ import java .util .concurrent .TimeUnit ;
42+ import java .util .concurrent .atomic .AtomicInteger ;
43+
44+ public class ContainerResponseWriterNoFlushTest {
45+ private static final String RESPONSE = "RESPONSE" ;
46+ private static AtomicInteger flushCounter = new AtomicInteger (0 );
47+ private static class TestResponseOutputStream extends ByteArrayOutputStream implements FlushedCloseable {
48+ private boolean closed = false ;
49+ @ Override
50+ public void close () throws IOException {
51+ if (!closed ) {
52+ closed = true ;
53+ flush ();
54+ super .close ();
55+ }
56+ }
57+
58+ @ Override
59+ public void flush () throws IOException {
60+ flushCounter .incrementAndGet ();
61+ }
62+ }
63+
64+ private static class TestContainerWriter implements ContainerResponseWriter {
65+ TestResponseOutputStream outputStream ;
66+ private final boolean buffering ;
67+
68+ private TestContainerWriter (boolean buffering ) {
69+ this .buffering = buffering ;
70+ }
71+
72+ @ Override
73+ public OutputStream writeResponseStatusAndHeaders (long contentLength , ContainerResponse responseContext )
74+ throws ContainerException {
75+ outputStream = new TestResponseOutputStream ();
76+ return outputStream ;
77+ }
78+
79+ @ Override
80+ public boolean suspend (long timeOut , TimeUnit timeUnit , TimeoutHandler timeoutHandler ) {
81+ return false ;
82+ }
83+
84+ @ Override
85+ public void setSuspendTimeout (long timeOut , TimeUnit timeUnit ) throws IllegalStateException {
86+ }
87+
88+ @ Override
89+ public void commit () {
90+ }
91+
92+ @ Override
93+ public void failure (Throwable error ) {
94+ throw new RuntimeException (error );
95+ }
96+
97+ @ Override
98+ public boolean enableResponseBuffering () {
99+ return buffering ;
100+ }
101+ }
102+
103+ @ Path ("/test" )
104+ public static class StreamResource {
105+
106+ @ GET
107+ @ Path (value = "/stream" )
108+ @ Produces (MediaType .TEXT_PLAIN )
109+ public Response stream () {
110+
111+ StreamingOutput stream = output -> {
112+ output .write (RESPONSE .getBytes (StandardCharsets .UTF_8 ));
113+ };
114+ return Response .ok (stream ).build ();
115+ }
116+ }
117+
118+ @ Test
119+ public void testWriterBuffering () {
120+ TestContainerWriter writer = new TestContainerWriter (true );
121+ testWriter (writer );
122+ }
123+
124+ @ Test
125+ public void testWriterNoBuffering () {
126+ TestContainerWriter writer = new TestContainerWriter (false );
127+ testWriter (writer );
128+ }
129+
130+ private void testWriter (TestContainerWriter writer ) {
131+ flushCounter .set (0 );
132+ RequestContextBuilder rcb = RequestContextBuilder .from ("/test/stream" , "GET" );
133+
134+ TestContainerRequest request = rcb .new TestContainerRequest (
135+ null , URI .create ("/test/stream" ), "GET" , null , new MapPropertiesDelegate ()) {
136+ @ Override
137+ public void setWorkers (MessageBodyWorkers workers ) {
138+ if (workers != null ) {
139+ setWriter (writer );
140+ }
141+ super .setWorkers (workers );
142+ }
143+ };
144+
145+ ApplicationHandler applicationHandler = new ApplicationHandler (new ResourceConfig (StreamResource .class ));
146+ Future <ContainerResponse > future = applicationHandler .apply (request );
147+ MatcherAssert .assertThat (writer .outputStream .toString (), Matchers .is (RESPONSE ));
148+ MatcherAssert .assertThat (flushCounter .get (), Matchers .is (1 ));
149+ }
150+ }
0 commit comments