diff --git a/src/main/java/org/eclipse/yasson/internal/JsonBinding.java b/src/main/java/org/eclipse/yasson/internal/JsonBinding.java index 5cacc63e..29394118 100644 --- a/src/main/java/org/eclipse/yasson/internal/JsonBinding.java +++ b/src/main/java/org/eclipse/yasson/internal/JsonBinding.java @@ -12,6 +12,9 @@ package org.eclipse.yasson.internal; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.FilterReader; import java.io.FilterWriter; import java.io.InputStream; import java.io.OutputStream; @@ -74,7 +77,7 @@ public T fromJson(String str, Type type) throws JsonbException { @Override public T fromJson(Reader reader, Class type) throws JsonbException { - try (JsonParser parser = jsonbContext.getJsonProvider().createParser(reader)) { + try (JsonParser parser = jsonbContext.getJsonProvider().createParser(new CloseSuppressingReader(reader))) { DeserializationContextImpl unmarshaller = new DeserializationContextImpl(jsonbContext); return deserialize(type, parser, unmarshaller); } @@ -82,7 +85,7 @@ public T fromJson(Reader reader, Class type) throws JsonbException { @Override public T fromJson(Reader reader, Type type) throws JsonbException { - try (JsonParser parser = jsonbContext.getJsonProvider().createParser(reader)) { + try (JsonParser parser = jsonbContext.getJsonProvider().createParser(new CloseSuppressingReader(reader))) { DeserializationContextImpl unmarshaller = new DeserializationContextImpl(jsonbContext); return deserialize(type, parser, unmarshaller); } @@ -91,7 +94,7 @@ public T fromJson(Reader reader, Type type) throws JsonbException { @Override public T fromJson(InputStream stream, Class clazz) throws JsonbException { DeserializationContextImpl unmarshaller = new DeserializationContextImpl(jsonbContext); - try (JsonParser parser = inputStreamParser(stream)) { + try (JsonParser parser = inputStreamParser(new CloseSuppressingInputStream(stream))) { return deserialize(clazz, parser, unmarshaller); } } @@ -99,7 +102,7 @@ public T fromJson(InputStream stream, Class clazz) throws JsonbException @Override public T fromJson(InputStream stream, Type type) throws JsonbException { DeserializationContextImpl unmarshaller = new DeserializationContextImpl(jsonbContext); - try (JsonParser parser = inputStreamParser(stream)) { + try (JsonParser parser = inputStreamParser(new CloseSuppressingInputStream(stream))) { return deserialize(type, parser, unmarshaller); } } @@ -170,7 +173,7 @@ private JsonGenerator writerGenerator(Writer writer) { @Override public void toJson(Object object, OutputStream stream) throws JsonbException { final SerializationContextImpl marshaller = new SerializationContextImpl(jsonbContext); - try (JsonGenerator generator = streamGenerator(stream)) { + try (JsonGenerator generator = streamGenerator(new CloseSuppressingOutputStream(stream))) { marshaller.marshall(object, generator); } } @@ -178,7 +181,7 @@ public void toJson(Object object, OutputStream stream) throws JsonbException { @Override public void toJson(Object object, Type type, OutputStream stream) throws JsonbException { final SerializationContextImpl marshaller = new SerializationContextImpl(jsonbContext, type); - try (JsonGenerator generator = streamGenerator(stream)) { + try (JsonGenerator generator = streamGenerator(new CloseSuppressingOutputStream(stream))) { marshaller.marshall(object, generator); } } @@ -248,4 +251,43 @@ public void close() { } + private static class CloseSuppressingOutputStream extends FilterOutputStream { + + protected CloseSuppressingOutputStream(OutputStream out) { + super(out); + } + + @Override + public void close() { + // do not close + } + + } + + private static class CloseSuppressingInputStream extends FilterInputStream { + + protected CloseSuppressingInputStream(InputStream in) { + super(in); + } + + @Override + public void close() { + // do not close + } + + } + + private static class CloseSuppressingReader extends FilterReader { + + protected CloseSuppressingReader(Reader in) { + super(in); + } + + @Override + public void close() { + // do not close + } + + } + } diff --git a/src/test/java/org/eclipse/yasson/Issue586Test.java b/src/test/java/org/eclipse/yasson/Issue586Test.java new file mode 100644 index 00000000..b2a741bf --- /dev/null +++ b/src/test/java/org/eclipse/yasson/Issue586Test.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025 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, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.yasson; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.ByteArrayInputStream; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.FilterReader; +import java.io.FilterWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; + +/** + * Verify Jsonb leaves the streams open. + * + * @author Philippe Marschall + */ +public class Issue586Test { + + @Test + public void fromJsonInputStreamClass() { + CloseRememberingInputStream stream = new CloseRememberingInputStream(asInputStream("{\"field\":\"first\"}")); + assertNotNull(Jsonbs.defaultJsonb.fromJson(stream, KeyValue.class)); + assertFalse(stream.isCloseCalled(), "close() should not be called by "); + } + + @Test + public void fromJsonInputStreamType() { + CloseRememberingInputStream stream = new CloseRememberingInputStream(asInputStream("{\"field\":\"first\"}")); + assertNotNull(Jsonbs.defaultJsonb.fromJson(stream, (Type) KeyValue.class)); + assertFalse(stream.isCloseCalled(), "close() should not be called by "); + } + + @Test + public void fromJsonReaderClass() { + CloseRememberingReader stream = new CloseRememberingReader(asReader("{\"field\":\"first\"}")); + assertNotNull(Jsonbs.defaultJsonb.fromJson(stream, KeyValue.class)); + assertFalse(stream.isCloseCalled(), "close() should not be called by "); + } + + @Test + public void fromJsonReaderType() { + CloseRememberingReader reader = new CloseRememberingReader(asReader("{\"field\":\"first\"}")); + assertNotNull(Jsonbs.defaultJsonb.fromJson(reader, (Type) KeyValue.class)); + assertFalse(reader.isCloseCalled(), "close() should not be called by "); + } + @Test + public void toJsonOutputStreamObject() { + CloseRememberingOutputStream stream = new CloseRememberingOutputStream(OutputStream.nullOutputStream()); + Jsonbs.defaultJsonb.toJson(new KeyValue("first"), stream); + assertFalse(stream.isCloseCalled(), "close() should not be called by "); + } + + @Test + public void toJsonOutputStreamObjectAndType() { + CloseRememberingOutputStream stream = new CloseRememberingOutputStream(OutputStream.nullOutputStream()); + Jsonbs.defaultJsonb.toJson(new KeyValue("first"), KeyValue.class, stream); + assertFalse(stream.isCloseCalled(), "close() should not be called by "); + } + + @Test + public void toJsonWriterObject() { + CloseRememberingWriter writer = new CloseRememberingWriter(Writer.nullWriter()); + Jsonbs.defaultJsonb.toJson(new KeyValue("first"), writer); + assertFalse(writer.isCloseCalled(), "close() should not be called by "); + } + + @Test + public void toJsonWriterObjectAndType() { + CloseRememberingWriter writer= new CloseRememberingWriter(Writer.nullWriter()); + Jsonbs.defaultJsonb.toJson(new KeyValue("first"), KeyValue.class, writer); + assertFalse(writer.isCloseCalled(), "close() should not be called by "); + } + + private static InputStream asInputStream(String s) { + return new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)); + } + + private static Reader asReader(String s) { + return new StringReader(s); + } + + public static class KeyValue { + + public String field; + + public KeyValue() { + + } + + public KeyValue(String field) { + this.field = field; + } + } + + static final class CloseRememberingOutputStream extends FilterOutputStream { + + private boolean closeCalled; + + CloseRememberingOutputStream(OutputStream out) { + super(out); + this.closeCalled = false; + } + + @Override + public void close() throws IOException { + closeCalled = true; + super.close(); + } + + boolean isCloseCalled() { + return closeCalled; + } + + } + + static final class CloseRememberingInputStream extends FilterInputStream { + + private boolean closeCalled; + + CloseRememberingInputStream(InputStream in) { + super(in); + this.closeCalled = false; + } + + @Override + public void close() throws IOException { + closeCalled = true; + super.close(); + } + + boolean isCloseCalled() { + return closeCalled; + } + + } + + static final class CloseRememberingReader extends FilterReader { + + private boolean closeCalled; + + CloseRememberingReader(Reader in) { + super(in); + this.closeCalled = false; + } + + @Override + public void close() throws IOException { + closeCalled = true; + super.close(); + } + + boolean isCloseCalled() { + return closeCalled; + } + + } + + static final class CloseRememberingWriter extends FilterWriter { + + private boolean closeCalled; + + CloseRememberingWriter(Writer out) { + super(out); + this.closeCalled = false; + } + + @Override + public void close() throws IOException { + closeCalled = true; + super.close(); + } + + boolean isCloseCalled() { + return closeCalled; + } + + } + +}