diff --git a/NOTICE.md b/NOTICE.md index 2818e63200..f30b6adbe4 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -57,25 +57,25 @@ Bootstrap v3.3.7 * Project: http://getbootstrap.com * Copyright: 2011-2016 Twitter, Inc -Google Guava Version 18.0 +Google Guava Version 33.3.0-jre * License: Apache License, 2.0 -* Copyright (C) 2009 The Guava Authors +* Copyright (C) 2009, 2024 The Guava Authors -jakarta.inject Version: 1 +jakarta.inject Version: 2.0.1 * License: Apache License, 2.0 -* Copyright (C) 2009 The JSR-330 Expert Group +* Copyright (C) 2009, 2021 The JSR-330 Expert Group Javassist Version 3.30.2-GA * License: Apache License, 2.0 * Project: http://www.javassist.org/ * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. -Jackson JAX-RS Providers Version 2.17.1 +Jackson JAX-RS Providers Version 2.17.2 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2024 FasterXML, LLC. All rights reserved unless otherwise indicated. -jQuery v1.12.4 +jQuery v3.7.1 * License: jquery.org/license * Project: jquery.org * Copyright: (c) jQuery Foundation diff --git a/core-common/src/main/java/org/glassfish/jersey/ApplicationSupplier.java b/core-common/src/main/java/org/glassfish/jersey/ApplicationSupplier.java index 1eb9ca5c72..6ef17b9718 100644 --- a/core-common/src/main/java/org/glassfish/jersey/ApplicationSupplier.java +++ b/core-common/src/main/java/org/glassfish/jersey/ApplicationSupplier.java @@ -17,7 +17,7 @@ package org.glassfish.jersey; -import javax.ws.rs.core.Application; +import jakarta.ws.rs.core.Application; /** * Implementation of this interface is capable of returning {@link Application}. 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 0000000000..f913ae650d --- /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 0000000000..12aa7144d8 --- /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. + * + *

+ * Usable by {@link javax.ws.rs.client.ClientRequestContext#setEntityStream(OutputStream)}. + * Usable by {@link javax.ws.rs.container.ContainerResponseContext#setEntityStream(OutputStream)}. + *

+ * + *

+ * 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. + *

+ */ +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. + * + *

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 + * mark 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 0000000000..7a70945f25 --- /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 c69f173f9a..b1b7745bbd 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 @@ -46,6 +46,7 @@ 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. @@ -561,11 +562,13 @@ public void close() { 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/examples/NOTICE.md b/examples/NOTICE.md index 034205c6f3..6e93f35ec7 100644 --- a/examples/NOTICE.md +++ b/examples/NOTICE.md @@ -39,8 +39,8 @@ aopalliance Version 1 Bean Validation API 3.0.2 * License: Apache License, 2.0 -* Project: http://beanvalidation.org/1.1/ -* Copyright: 2009, Red Hat, Inc. and/or its affiliates, and individual contributors +* Project: http://beanvalidation.org/3.0/ +* Copyright: 2009, 2020 Red Hat, Inc. and/or its affiliates, and individual contributors * by the @authors tag. Hibernate Validator CDI, 7.0.5.Final @@ -58,25 +58,25 @@ CDI API Version 3.0 * Project: http://www.seamframework.org/Weld * Copyright 2010, Red Hat, Inc., and individual contributors by the @authors tag. -Google Guava Version 18.0 +Google Guava Version 33.3.0-jre * License: Apache License, 2.0 -* Copyright (C) 2009 The Guava Authors +* Copyright (C) 2009, 2024 The Guava Authors -jakarta.inject Version: 1 +jakarta.inject Version: 2.0.1 * License: Apache License, 2.0 -* Copyright (C) 2009 The JSR-330 Expert Group +* Copyright (C) 2009, 2021 The JSR-330 Expert Group Javassist Version 3.30.2-GA * License: Apache License, 2.0 * Project: http://www.javassist.org/ * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. -Jackson JAX-RS Providers Version 2.17.1 +Jackson JAX-RS Providers Version 2.17.2 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2023 FasterXML, LLC. All rights reserved unless otherwise indicated. -jQuery v1.12.4 +jQuery v3.7.1 * License: jquery.org/license * Project: jquery.org * Copyright: (c) jQuery Foundation diff --git a/examples/extended-wadl-webapp/pom.xml b/examples/extended-wadl-webapp/pom.xml index 0b5f9f2db1..605b4d68b2 100644 --- a/examples/extended-wadl-webapp/pom.xml +++ b/examples/extended-wadl-webapp/pom.xml @@ -108,8 +108,8 @@ org.slf4j - slf4j-log4j12 - 2.0.13 + slf4j-reload4j + ${slf4j.version} test diff --git a/examples/osgi-http-service/functional-test/pom.xml b/examples/osgi-http-service/functional-test/pom.xml index 05b2447c27..762ba76645 100644 --- a/examples/osgi-http-service/functional-test/pom.xml +++ b/examples/osgi-http-service/functional-test/pom.xml @@ -144,8 +144,8 @@ org.slf4j - slf4j-log4j12 - 2.0.13 + slf4j-reload4j + ${slf4j.version} test diff --git a/examples/servlet3-webapp/pom.xml b/examples/servlet3-webapp/pom.xml index 3c6bd334ac..1e2a7dbee2 100644 --- a/examples/servlet3-webapp/pom.xml +++ b/examples/servlet3-webapp/pom.xml @@ -112,6 +112,15 @@ + + jdk8_tests + + 1.8 + + + ${junit5.jdk8.version} + + pre-release diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/SupplierThreadScopeClassBean.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/SupplierThreadScopeClassBean.java index 0326efe78a..bddd48475a 100644 --- a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/SupplierThreadScopeClassBean.java +++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/SupplierThreadScopeClassBean.java @@ -25,9 +25,9 @@ import org.jboss.weld.bean.proxy.ProxyFactory; import org.jboss.weld.manager.BeanManagerImpl; -import javax.enterprise.context.Dependent; -import javax.enterprise.context.spi.CreationalContext; -import javax.ws.rs.RuntimeType; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.ws.rs.RuntimeType; import java.lang.annotation.Annotation; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/ThreadScopeBeanInstance.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/ThreadScopeBeanInstance.java index 30d18964ab..73ac6514aa 100644 --- a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/ThreadScopeBeanInstance.java +++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/ThreadScopeBeanInstance.java @@ -19,8 +19,8 @@ import org.jboss.weld.bean.StringBeanIdentifier; import org.jboss.weld.bean.proxy.ContextBeanInstance; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.PassivationCapable; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.PassivationCapable; import java.lang.reflect.Method; import java.util.WeakHashMap; import java.util.function.Supplier; diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeClassBean.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeClassBean.java index 28e69e7243..698bfb034d 100644 --- a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeClassBean.java +++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeClassBean.java @@ -20,8 +20,8 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import javax.enterprise.context.Dependent; -import javax.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; import org.glassfish.jersey.internal.inject.SupplierClassBinding; import org.glassfish.jersey.internal.inject.SupplierInstanceBinding; diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/ThreadScopeBeanInstance.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/ThreadScopeBeanInstance.java index 3067386e45..a2009609f2 100644 --- a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/ThreadScopeBeanInstance.java +++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/ThreadScopeBeanInstance.java @@ -23,8 +23,8 @@ import java.util.WeakHashMap; import java.util.function.Supplier; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.PassivationCapable; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.PassivationCapable; /** * {@link org.glassfish.jersey.internal.inject.PerThread} scope bean instance used from diff --git a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java index 39e93e177e..2edb683ae5 100644 --- a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java +++ b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java @@ -16,6 +16,8 @@ package org.glassfish.jersey.jsonb; +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.Configuration; import jakarta.ws.rs.core.Feature; import jakarta.ws.rs.core.FeatureContext; diff --git a/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java index c2b63779d6..7dc8a3f6a6 100644 --- a/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java +++ b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java @@ -27,9 +27,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import javax.ws.rs.RuntimeType; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.FeatureContext; +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.FeatureContext; import java.lang.reflect.Proxy; import java.util.concurrent.atomic.AtomicReference; diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java index 5d328da992..56b1bf6480 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java @@ -11,7 +11,7 @@ */ public final class PackageVersion implements Versioned { public final static Version VERSION = VersionUtil.parseVersion( - "2.17.1", "com.fasterxml.jackson.jaxrs", "jackson-jaxrs-json-provider"); + "2.17.2", "com.fasterxml.jackson.jaxrs", "jackson-jaxrs-json-provider"); @Override public Version version() { diff --git a/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown b/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown index 4edecfc595..9440229038 100644 --- a/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown +++ b/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown @@ -31,7 +31,7 @@ The project maintains the following source code repositories: ## Third-party Content -Jackson JAX-RS Providers version 2.17.1 +Jackson JAX-RS Providers version 2.17.2 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2023 FasterXML, LLC. All rights reserved unless otherwise indicated. diff --git a/pom.xml b/pom.xml index b37f5c03e9..54ad639d1b 100644 --- a/pom.xml +++ b/pom.xml @@ -2155,13 +2155,13 @@ 3.1.0 1.10.14 3.7.1 - 3.3.2 - 3.4.1 - 3.2.0 - 3.5.0 + 3.4.0 + 3.5.0 + 3.4.1 + 3.6.0 3.2.0 - 3.3.1 - 10.16.0 + 3.4.0 + 10.17.0 3.13.0 3.9.0 - 2.8.0 - 3.6.1 + 2.8.1 + 3.7.1 3.1.2 3.3.0 - 3.2.5 + 3.3.1 5.1.9 3.0.5 5.1 3.1.2 4.2.0 - 3.4.1 - 3.6.3 - 3.3.2 + 3.4.2 + 3.8.0 + 3.4.0 1.2.4 - 3.5.0 + 3.6.2 3.3.1 - 3.5.3 + 3.6.0 3.3.1 - 3.2.5 + 3.3.1 3.4.0 2.11.0 1.1.0 @@ -2203,22 +2203,22 @@ 9.7 - 1.9.22 + 1.9.22.1 1.70 2.16.1 1.16.1 - 1.3.1 + 1.3.3 1.7.0 1.6.4 2.8.4 7.0.5 1.7 - 2.3.32 - 2.0.26 - 4.0.21 - 2.10.1 + 2.3.33 + 2.0.29 + 4.0.22 + 2.11.0 @@ -2238,14 +2238,14 @@ 1.4.14 3.7.1 - 33.1.0-jre - 2.2 + 33.3.0-jre + 3.0 2.10.0 org.glassfish.hk2.*;version="[2.5,4)" org.jvnet.hk2.*;version="[2.5,4)" 4.5.14 5.3.1 - 2.17.1 + 2.17.2 3.30.2-GA 3.4.3.Final 1.19.3 @@ -2257,12 +2257,13 @@ 1.37 1.49 4.13.2 - 5.10.2 - 1.10.2 + 5.11.0 + 5.10.3 + 1.11.0 4.0.3 4.11.0 - 0.9.12 - 4.1.109.Final + 0.9.14 + 4.1.112.Final 0.33.0 6.0.0 1.10.0 @@ -2274,9 +2275,9 @@ 1.3.8 2.2.21 6.0.1 - 2.0.13 + 2.0.16 6.0.18 - 7.9.0 + 7.10.2 6.9.13.6 3.1.2.RELEASE @@ -2288,7 +2289,7 @@ 2.12.2 - 20.3.14 + 20.3.15 6.2.5 @@ -2325,7 +2326,7 @@ org.eclipse.jetty.*;version="[11,15)" 11.0.22 11.0.15 - 9.4.54.v20240208 + 9.4.55.v20240627 11.0.20 6.1.14 2.0.0 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 bd6e3c531a..817170c43b 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 jakarta.ws.rs.core.MediaType; import jakarta.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 @@ public void testCopyConstructor() { 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 { + + } } +