Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
* Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
package com.marklogic.client.example.handle;

Expand Down Expand Up @@ -84,21 +84,20 @@ public static void runStrongTyped(ExampleProperties props) throws IOException {
// create a manager for JSON documents
JSONDocumentManager docMgr = client.newJSONDocumentManager();

// read the example file
InputStream docStream = Util.openStream("data"+File.separator+filename);
if (docStream == null)
throw new IOException("Could not read document example");

// create an identifier for the document
String docId = "/example/"+filename;

// create a handle for the document
JacksonHandle writeHandle = new JacksonHandle();

// parse the example file with Jackson
JsonNode writeDocument = writeHandle.getMapper().readValue(
new InputStreamReader(docStream, "UTF-8"), JsonNode.class);
writeHandle.set(writeDocument);
// read the example file and parse it with Jackson
try (InputStream docStream = Util.openStream("data"+File.separator+filename)) {
if (docStream == null)
throw new IOException("Could not read document example");
JsonNode writeDocument = writeHandle.getMapper().readValue(
new InputStreamReader(docStream, "UTF-8"), JsonNode.class);
writeHandle.set(writeDocument);
}

// write the document
docMgr.write(docId, writeHandle);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
* Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/

package com.marklogic.client.datamovement;
Expand All @@ -19,6 +19,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.util.ArrayDeque;
import java.util.Spliterator;
import java.util.Spliterators;
Expand Down Expand Up @@ -97,7 +98,21 @@ public Stream<T> split(InputStream input) throws IOException {
}

JsonParser jsonParser = new JsonFactory().createParser(input);
return split(jsonParser);
try {
return split(jsonParser).onClose(() -> closeParser(jsonParser));
} catch (IOException | RuntimeException e) {
jsonParser.close();
throw e;
}
}

// Closes a parser that this class created, so that closing the returned Stream releases it.
private static void closeParser(JsonParser jsonParser) {
try {
jsonParser.close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

/**
Expand Down Expand Up @@ -127,7 +142,12 @@ public Stream<DocumentWriteOperation> splitWriteOperations(InputStream input, St
}

JsonParser jsonParser = new JsonFactory().createParser(input);
return splitWriteOperations(jsonParser, splitFilename);
try {
return splitWriteOperations(jsonParser, splitFilename).onClose(() -> closeParser(jsonParser));
} catch (RuntimeException e) {
jsonParser.close();
throw e;
}
}

/**
Expand Down Expand Up @@ -251,11 +271,10 @@ public String serialize(JsonParser containerParser) {
throw new IllegalArgumentException("JsonParser cannot be null");
}

try {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = new JsonFactory().createGenerator(stringWriter);
try (StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = new JsonFactory().createGenerator(stringWriter)) {
jsonGenerator.copyCurrentStructure(containerParser);
jsonGenerator.close();
jsonGenerator.flush();
return stringWriter.toString();
} catch (IOException e) {
throw new RuntimeException("Could not serialize the document", e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
* Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
package com.marklogic.client.io;

Expand Down Expand Up @@ -197,9 +197,9 @@ protected boolean hasContent() {
public void write(OutputStream out) throws IOException {
try {
if (parser != null && parser.nextToken() != null) {
JsonGenerator generator = getMapper().getFactory().createGenerator(out);
generator.copyCurrentStructure(parser);
generator.close();
try (JsonGenerator generator = getMapper().getFactory().createGenerator(out)) {
generator.copyCurrentStructure(parser);
}
} else if (content != null) {
byte[] b = new byte[BUFFER_SIZE];
int len = 0;
Expand Down Expand Up @@ -248,9 +248,9 @@ private OutputStreamSenderImpl(ObjectMapper mapper, JsonParser parser) {
}
@Override
public void write(OutputStream out) throws IOException {
JsonGenerator generator = mapper.getFactory().createGenerator(out);
generator.copyCurrentStructure(parser);
generator.close();
try (JsonGenerator generator = mapper.getFactory().createGenerator(out)) {
generator.copyCurrentStructure(parser);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
* Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
package com.marklogic.client.pojo.util;

Expand Down Expand Up @@ -176,15 +176,15 @@ private static void generateConfig(String[] classes, ObjectMapper mapper, Writer
errorExactPair(className);
}
}
JsonGenerator config = mapper.getFactory().createGenerator(out);
config.useDefaultPrettyPrinter();
config.writeStartObject();
generatePathIndexes(paths, config);
generateGeoPathIndexes(geoPaths, config);
generateGeoPairIndexes(geoPairs, config);
config.writeEndObject();
config.flush();
config.close();
try (JsonGenerator config = mapper.getFactory().createGenerator(out)) {
config.useDefaultPrettyPrinter();
config.writeStartObject();
generatePathIndexes(paths, config);
generateGeoPathIndexes(geoPaths, config);
generateGeoPairIndexes(geoPairs, config);
config.writeEndObject();
config.flush();
}
}

private static void errorExactPair(String className) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
* Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/

package com.marklogic.client.test.datamovement;
Expand All @@ -11,9 +11,14 @@
import com.marklogic.client.io.marker.JSONWriteHandle;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;
Expand Down Expand Up @@ -321,4 +326,64 @@ public NodeOperation startArray(String containerKey) {
return NodeOperation.DESCEND;
}
}

@Test
public void splitClosesInputStreamWhenStreamIsClosed() throws Exception {
JSONSplitter<StringHandle> splitter = JSONSplitter.makeArraySplitter();
CloseTrackingInputStream input = new CloseTrackingInputStream(new ByteArrayInputStream(largeJsonArray()));

try (Stream<StringHandle> stream = splitter.split(input)) {
assertTrue(stream.findFirst().isPresent());
assertFalse(input.closed.get(), "The input stream should remain open until the stream is closed");
}

assertTrue(input.closed.get(), "Closing the stream should close the underlying input stream");
}

@Test
public void splitWriteOperationsClosesInputStreamWhenStreamIsClosed() throws Exception {
JSONSplitter<StringHandle> splitter = JSONSplitter.makeArraySplitter();
CloseTrackingInputStream input = new CloseTrackingInputStream(new ByteArrayInputStream(largeJsonArray()));

try (Stream<DocumentWriteOperation> stream = splitter.splitWriteOperations(input)) {
assertTrue(stream.findFirst().isPresent());
assertFalse(input.closed.get(), "The input stream should remain open until the stream is closed");
}

assertTrue(input.closed.get(), "Closing the stream should close the underlying input stream");
}

// Large enough that the parser does not reach the end of the input (and auto-close it) after the first element.
private static byte[] largeJsonArray() {
StringBuilder json = new StringBuilder("[");
for (int i = 0; i < 5000; i++) {
json.append(i > 0 ? "," : "").append("{\"record\":").append(i).append("}");
}
return json.append("]").toString().getBytes(StandardCharsets.UTF_8);
}

private static class CloseTrackingInputStream extends InputStream {
final AtomicBoolean closed = new AtomicBoolean(false);
private final InputStream delegate;

CloseTrackingInputStream(InputStream delegate) {
this.delegate = delegate;
}

@Override
public int read() throws IOException {
return delegate.read();
}

@Override
public int read(byte[] b, int off, int len) throws IOException {
return delegate.read(b, off, len);
}

@Override
public void close() throws IOException {
closed.set(true);
delegate.close();
}
}
}
Loading