Skip to content

Commit 7c00171

Browse files
llingllinggit
authored andcommitted
add anyDocument interfaces and unit tests
1 parent 68456e2 commit 7c00171

File tree

14 files changed

+390
-16
lines changed

14 files changed

+390
-16
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputCaller.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ static <I> InputCaller<I> on(DatabaseClient client, JSONWriteHandle apiDecl, Buf
4141
return new InputEndpointImpl(client, apiDecl, new HandleProvider.ContentHandleProvider<>(inputHandle,null));
4242
}
4343

44+
/**
45+
* Constructs an instance of the InputCaller interface.
46+
* This factory is useful primarily for parameters or return values of the anyDocument type.
47+
* @param client the database client to use for making calls
48+
* @param apiDecl the JSON api declaration specifying how to call the endpoint
49+
* @param inputHandle the handles that provides the input content (such as StringHandle)
50+
* @param <IC> the content type of the input handle
51+
* @param <IR> the type for the data received by the input handle
52+
* @param <I> the input handle
53+
* @return the InputOutputCaller instance for calling the endpoint.
54+
*/
55+
static <IC,IR,I extends BufferableContentHandle<IC,IR>> InputCaller<I> onHandles(
56+
DatabaseClient client, JSONWriteHandle apiDecl, I inputHandle
57+
) {
58+
return new InputEndpointImpl(client, apiDecl, new HandleProvider.DirectHandleProvider<>(inputHandle, null));
59+
}
60+
4461
/**
4562
* Makes one call to an endpoint that doesn't take endpoint constants, endpoint state, or a session.
4663
* @param input the request data sent to the endpoint

marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputCaller.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,28 @@ static <I,O> InputOutputCaller<I,O> on(
5050
return new InputOutputEndpointImpl<>(client, apiDecl, new HandleProvider.ContentHandleProvider<>(inputHandle, outputHandle));
5151
}
5252

53+
/**
54+
* Constructs an instance of the InputOutputCaller interface.
55+
* This factory is useful primarily for parameters or return values of the anyDocument type.
56+
* @param client the database client to use for making calls
57+
* @param apiDecl the JSON api declaration specifying how to call the endpoint
58+
* @param inputHandle the handles that provides the input content (such as StringHandle)
59+
* @param outputHandle the handles that provides the output content (such as BytesHandle)
60+
* @param <IC> the content type of the input handle
61+
* @param <IR> the type for the data received by the input handle
62+
* @param <OC> the content type of the output handle
63+
* @param <OR> the type for the data received by the output handle
64+
* @param <I> the input handle
65+
* @param <O> the output handle
66+
* @return the InputOutputCaller instance for calling the endpoint.
67+
*/
68+
static <IC,IR,OC,OR,I extends BufferableContentHandle<IC,IR>,O extends BufferableContentHandle<OC,OR>> InputOutputCaller<I,O> onHandles(
69+
DatabaseClient client, JSONWriteHandle apiDecl,
70+
I inputHandle, O outputHandle
71+
) {
72+
return new InputOutputEndpointImpl<I, O>(client, apiDecl, (HandleProvider<I, O>) new HandleProvider.DirectHandleProvider<IC,IR,OC,OR>(inputHandle, outputHandle));
73+
}
74+
5375
/**
5476
* Makes one call to an endpoint that doesn't take endpoint constants, endpoint state, or a session.
5577
* @param input the request data sent to the endpoint

marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputCaller.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ static <O> OutputCaller<O> on(
4444
return new OutputEndpointImpl(client, apiDecl, new HandleProvider.ContentHandleProvider<>(null, outputHandle));
4545
}
4646

47+
/**
48+
* Constructs an instance of the OutputCaller interface.
49+
* This factory is useful primarily for parameters or return values of the anyDocument type.
50+
* @param client the database client to use for making calls
51+
* @param apiDecl the JSON api declaration specifying how to call the endpoint
52+
* @param outputHandle the handles that provides the output content (such as BytesHandle)
53+
* @param <OC> the content type of the output handle
54+
* @param <OR> the type for the data received by the output handle
55+
* @param <O> the output handle
56+
* @return the InputOutputCaller instance for calling the endpoint.
57+
*/
58+
static <OC,OR,O extends BufferableContentHandle<OC,OR>> OutputCaller<O> onHandles(
59+
DatabaseClient client, JSONWriteHandle apiDecl, O outputHandle
60+
) {
61+
return new OutputEndpointImpl(client, apiDecl, new HandleProvider.DirectHandleProvider<>(null, outputHandle));
62+
}
63+
4764
/**
4865
* Makes one call to an endpoint that doesn't take endpoint constants, endpoint state, or a session.
4966
* @return the response data from the endpoint

marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/BaseCallerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ static abstract class ValuedefImpl {
5454

5555
if (!"session".equals(datatype)) {
5656
this.format = Format.getFromDataType(datatype);
57-
if (this.format == Format.UNKNOWN) {
57+
if (this.format == Format.UNKNOWN && !"anyDocument".equals(datatype)) {
5858
throw new IllegalArgumentException(
5959
"datatype must specify a document format: " + datatype
6060
);

marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOCallerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ abstract class IOCallerImpl<I,O> extends BaseCallerImpl {
164164
throw new IllegalArgumentException(
165165
"endpointState parameter requires return in endpoint: "+getEndpointPath()
166166
);
167-
} else if (this.endpointStateParamdef.getFormat() != this.returndef.getFormat()) {
167+
} else if (this.endpointStateParamdef.getFormat() != this.returndef.getFormat() && !"anyDocument".equals(this.returndef.getDataType())) {
168168
throw new IllegalArgumentException(
169169
"endpointState format must match return format in endpoint: "+getEndpointPath()
170170
);

marklogic-client-api/src/test/java/com/marklogic/client/test/dataservices/BulkIOEndpointTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import com.marklogic.client.dataservices.InputOutputCaller;
2424
import com.marklogic.client.document.JSONDocumentManager;
2525
import com.marklogic.client.impl.NodeConverter;
26+
import com.marklogic.client.io.Format;
2627
import com.marklogic.client.io.InputStreamHandle;
2728
import com.marklogic.client.io.JacksonHandle;
29+
import com.marklogic.client.io.StringHandle;
2830
import org.junit.Test;
2931

3032
import java.io.IOException;
@@ -158,6 +160,77 @@ public void testInputOutputCallerImpl() throws IOException {
158160
IOTestUtil.modMgr.delete(scriptPath, apiPath);
159161
}
160162

163+
@Test
164+
public void testInputOutputCallerImplAnyDoc() throws IOException {
165+
String apiName = "bulkIOAnyDocumentInputOutputCaller.api";
166+
167+
int nextStart = 1;
168+
int workMax = 4;
169+
170+
ObjectNode apiObj = IOTestUtil.readApi(apiName);
171+
String scriptPath = IOTestUtil.getScriptPath(apiObj);
172+
String apiPath = IOTestUtil.getApiPath(scriptPath);
173+
IOTestUtil.load(apiName, apiObj, scriptPath, apiPath);
174+
175+
int batchSize = apiObj.get("$bulk").get("inputBatchSize").asInt();
176+
int callCount = (workMax - nextStart) / batchSize +
177+
(((workMax - nextStart) % batchSize) > 0 ? 1 : 0);
178+
179+
String endpointState = "{\"next\":"+nextStart+"}";
180+
String endpointConstants = "{\"max\":"+workMax+"}";
181+
Set<String> input = IOTestUtil.setOf( // Set.of(
182+
"{\"docNum\":1, \"docName\":\"alpha\"}",
183+
"{\"docNum\":2, \"docName\":\"beta\"}",
184+
"{\"docNum\":3, \"docName\":\"gamma\"}"
185+
);
186+
187+
Set<String> output = new HashSet<>();
188+
InputOutputCaller<StringHandle, StringHandle> endpoint =
189+
InputOutputCaller.onHandles(IOTestUtil.db, new JacksonHandle(apiObj), new StringHandle(), new StringHandle());
190+
191+
InputOutputCaller.BulkInputOutputCaller<StringHandle, StringHandle> bulkCaller =
192+
endpoint.bulkCaller(endpoint.newCallContext()
193+
.withEndpointStateAs(endpointState)
194+
.withEndpointConstantsAs(endpointConstants));
195+
bulkCaller.setOutputListener(value -> {
196+
String v = value.toString();
197+
System.out.println("received: "+v);
198+
output.add(v);
199+
});
200+
201+
input.stream().forEach(value -> {
202+
System.out.println("adding "+value);
203+
bulkCaller.accept(new StringHandle(value).withFormat(Format.JSON));
204+
});
205+
206+
bulkCaller.awaitCompletion();
207+
208+
ObjectNode finalState = mapper.readValue(bulkCaller.getEndpointState(), ObjectNode.class);
209+
210+
assertEquals("mismatch between input and output size", input.size(), output.size());
211+
assertEquals("mismatch between input and output elements", input, output);
212+
213+
assertNotNull("null final state", finalState);
214+
assertTrue("final state not object", finalState.isObject());
215+
216+
JsonNode finalNext = finalState.get("next");
217+
assertNotNull("null final next", finalNext);
218+
assertTrue("final next not number", finalNext.isNumber());
219+
assertEquals("mismatch on final next", workMax, finalNext.asInt());
220+
221+
JsonNode finalMax = finalState.get("workMax");
222+
assertNotNull("null final max", finalMax);
223+
assertTrue("final max not number", finalMax.isNumber());
224+
assertEquals("mismatch on final max", workMax, finalMax.asInt());
225+
226+
// JsonNode sessionCounter = finalState.get("sessionCounter");
227+
// assertNotNull("null final sessionCounter", sessionCounter);
228+
// assertTrue("final sessionCounter not number", sessionCounter.isNumber());
229+
// assertEquals("mismatch on final sessionCounter", callCount - 1, sessionCounter.asInt());
230+
231+
IOTestUtil.modMgr.delete(scriptPath, apiPath);
232+
}
233+
161234
@Test
162235
public void testInputOutputCallerImplWithMultipleCallContexts() throws IOException {
163236
String apiName = "bulkIOInputOutputCaller.api";

marklogic-client-api/src/test/java/com/marklogic/client/test/dataservices/BulkIOInputCallerTest.java

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,27 @@
2020
import com.marklogic.client.dataservices.IOEndpoint;
2121
import com.marklogic.client.dataservices.InputCaller;
2222
import com.marklogic.client.document.JSONDocumentManager;
23+
import com.marklogic.client.impl.NodeConverter;
24+
import com.marklogic.client.io.Format;
2325
import com.marklogic.client.io.InputStreamHandle;
2426
import com.marklogic.client.io.JacksonHandle;
27+
import com.marklogic.client.io.StringHandle;
2528
import org.junit.AfterClass;
2629
import org.junit.BeforeClass;
2730
import org.junit.Test;
2831

2932
import java.io.InputStream;
3033
import java.util.HashMap;
3134
import java.util.Map;
35+
import java.util.Set;
3236
import java.util.stream.Stream;
3337

3438
import static org.junit.Assert.*;
3539

3640
public class BulkIOInputCallerTest {
3741

38-
static ObjectNode apiObj;
39-
static String apiName = "bulkIOInputCaller.api";
42+
static ObjectNode[] apiObj = new ObjectNode[2];
43+
static String[] apiNames = new String[]{"bulkIOInputCaller.api", "bulkIOAnyDocumentInputCaller.api"};
4044
static String scriptPath;
4145
static String apiPath;
4246
static JSONDocumentManager docMgr;
@@ -46,10 +50,13 @@ public class BulkIOInputCallerTest {
4650
@BeforeClass
4751
public static void setup() throws Exception {
4852
docMgr = IOTestUtil.db.newJSONDocumentManager();
49-
apiObj = IOTestUtil.readApi(apiName);
50-
scriptPath = IOTestUtil.getScriptPath(apiObj);
51-
apiPath = IOTestUtil.getApiPath(scriptPath);
52-
IOTestUtil.load(apiName, apiObj, scriptPath, apiPath);
53+
for (int i = 0; i < apiNames.length; i++) {
54+
String apiName = apiNames[i];
55+
apiObj[i] = IOTestUtil.readApi(apiName);
56+
scriptPath = IOTestUtil.getScriptPath(apiObj[i]);
57+
apiPath = IOTestUtil.getApiPath(scriptPath);
58+
IOTestUtil.load(apiName, apiObj[i], scriptPath, apiPath);
59+
}
5360
map = new HashMap<>();
5461
}
5562
@Test
@@ -61,7 +68,7 @@ public void bulkInputEndpointTestWithMultipleCallContexts() {
6168
String endpointState1 = "{\"next\":"+1+"}";
6269
String endpointConstants1 = "{\"max\":6,\"collection\":\"bulkInputTest_2\"}";
6370

64-
InputCaller<InputStream> loadEndpt = InputCaller.on(IOTestUtil.db, new JacksonHandle(apiObj), new InputStreamHandle());
71+
InputCaller<InputStream> loadEndpt = InputCaller.on(IOTestUtil.db, new JacksonHandle(apiObj[0]), new InputStreamHandle());
6572
IOEndpoint.CallContext[] callContextArray = {loadEndpt.newCallContext()
6673
.withEndpointStateAs(endpointState)
6774
.withEndpointConstantsAs(endpointConstants), loadEndpt.newCallContext()
@@ -90,6 +97,48 @@ public void bulkInputEndpointTestWithMultipleCallContexts() {
9097
map.get("bulkInputTest_2") >= 1);
9198
}
9299

100+
@Test
101+
public void bulkInputEndpointTestWithAnyDocument() {
102+
103+
String endpointState = "{\"next\":"+1+"}";
104+
String endpointConstants = "{\"max\":6,\"collection\":\"bulkInputTest_1\"}";
105+
106+
String endpointState1 = "{\"next\":"+1+"}";
107+
String endpointConstants1 = "{\"max\":6,\"collection\":\"bulkInputTest_2\"}";
108+
109+
InputCaller<StringHandle> loadEndpt = InputCaller.onHandles(IOTestUtil.db, new JacksonHandle(apiObj[1]), new StringHandle());
110+
IOEndpoint.CallContext[] callContextArray = {loadEndpt.newCallContext()
111+
.withEndpointStateAs(endpointState)
112+
.withEndpointConstantsAs(endpointConstants), loadEndpt.newCallContext()
113+
.withEndpointStateAs(endpointState1)
114+
.withEndpointConstantsAs(endpointConstants1)};
115+
InputCaller.BulkInputCaller<StringHandle> loader = loadEndpt.bulkCaller(callContextArray);
116+
117+
Set<String> input = IOTestUtil.setOf( // Set.of(
118+
"{\"docNum\":1, \"docName\":\"doc1\"}",
119+
"{\"docNum\":2, \"docName\":\"doc2\"}",
120+
"{\"docNum\":3, \"docName\":\"doc3\"}",
121+
"{\"docNum\":4, \"docName\":\"doc4\"}",
122+
"{\"docNum\":5, \"docName\":\"doc5\"}",
123+
"{\"docNum\":6, \"docName\":\"doc6\"}",
124+
"{\"docNum\":7, \"docName\":\"doc7\"}",
125+
"{\"docNum\":8, \"docName\":\"doc8\"}"
126+
);
127+
128+
input.stream().forEach(value -> {
129+
//System.out.println("adding "+value);
130+
loader.accept(new StringHandle(value).withFormat(Format.JSON));
131+
});
132+
loader.awaitCompletion();
133+
checkDocuments("bulkInputTest_1");
134+
checkDocuments("bulkInputTest_2");
135+
assertTrue("Number of documents written not as expected.", counter == 4);
136+
assertTrue("No documents written by first callContext in - bulkInputTest_1 collection.",
137+
map.get("bulkInputTest_1") >= 1);
138+
assertTrue("No documents written by second callContext in - bulkInputTest_2 collection.",
139+
map.get("bulkInputTest_2") >= 1);
140+
}
141+
93142
@AfterClass
94143
public static void cleanup(){
95144
IOTestUtil.modMgr.delete(scriptPath, apiPath);

marklogic-client-api/src/test/java/com/marklogic/client/test/dataservices/BulkIOOutputCallerTest.java

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
import static org.junit.Assert.assertTrue;
2727

2828
public class BulkIOOutputCallerTest {
29-
static ObjectNode apiObj;
30-
static String apiName = "bulkIOOutputCaller.api";
29+
static ObjectNode[] apiObj = new ObjectNode[2];
30+
static String[] apiNames = new String[]{"bulkIOOutputCaller.api", "bulkIOAnyDocumentOutputCaller.api"};
3131
static String scriptPath;
3232
static String apiPath;
3333
static JSONDocumentManager docMgr;
@@ -42,10 +42,13 @@ public class BulkIOOutputCallerTest {
4242
@BeforeClass
4343
public static void setup() throws Exception {
4444
docMgr = IOTestUtil.db.newJSONDocumentManager();
45-
apiObj = IOTestUtil.readApi(apiName);
46-
scriptPath = IOTestUtil.getScriptPath(apiObj);
47-
apiPath = IOTestUtil.getApiPath(scriptPath);
48-
IOTestUtil.load(apiName, apiObj, scriptPath, apiPath);
45+
for (int i = 0; i < apiNames.length; i++) {
46+
String apiName = apiNames[i];
47+
apiObj[i] = IOTestUtil.readApi(apiName);
48+
scriptPath = IOTestUtil.getScriptPath(apiObj[i]);
49+
apiPath = IOTestUtil.getApiPath(scriptPath);
50+
IOTestUtil.load(apiName, apiObj[i], scriptPath, apiPath);
51+
}
4952
writeDocuments(10,20, collectionName_1);
5053
writeDocuments(30,40, collectionName_2);
5154
}
@@ -58,7 +61,7 @@ public void bulkOutputCallerTestWithMultipleCallContexts() {
5861

5962
String endpointState2 = "{\"next\":"+1+"}";
6063
String endpointConstants2 = "{\"max\":6,\"limit\":5,\"collection\":\"bulkOutputTest_2\"}";
61-
OutputCaller<InputStream> endpoint = OutputCaller.on(IOTestUtil.db, new JacksonHandle(apiObj), new InputStreamHandle());
64+
OutputCaller<InputStream> endpoint = OutputCaller.on(IOTestUtil.db, new JacksonHandle(apiObj[0]), new InputStreamHandle());
6265
IOEndpoint.CallContext[] callContextArray = {endpoint.newCallContext()
6366
.withEndpointStateAs(endpointState2)
6467
.withEndpointConstantsAs(endpointConstants2), endpoint.newCallContext()
@@ -89,6 +92,44 @@ public void bulkOutputCallerTestWithMultipleCallContexts() {
8992
assertEquals("unexpected output values", expected, actual);
9093
}
9194

95+
@Test
96+
public void bulkOutputCallerTestWithAnyDocuments() {
97+
98+
String endpointState1 = "{\"next\":"+1+"}";
99+
String endpointConstants1 = "{\"max\":6,\"limit\":5,\"collection\":\"bulkOutputTest_1\"}";
100+
101+
String endpointState2 = "{\"next\":"+1+"}";
102+
String endpointConstants2 = "{\"max\":6,\"limit\":5,\"collection\":\"bulkOutputTest_2\"}";
103+
OutputCaller<StringHandle> endpoint = OutputCaller.onHandles(IOTestUtil.db, new JacksonHandle(apiObj[1]), new StringHandle());
104+
IOEndpoint.CallContext[] callContextArray = {endpoint.newCallContext()
105+
.withEndpointStateAs(endpointState2)
106+
.withEndpointConstantsAs(endpointConstants2), endpoint.newCallContext()
107+
.withEndpointStateAs(endpointState1)
108+
.withEndpointConstantsAs(endpointConstants1)};
109+
OutputCaller.BulkOutputCaller<StringHandle> bulkCaller = endpoint.bulkCaller(callContextArray);
110+
Set<String> actual = new ConcurrentSkipListSet<>();
111+
final AtomicBoolean duplicated = new AtomicBoolean(false);
112+
final AtomicBoolean exceptional = new AtomicBoolean(false);
113+
bulkCaller.setOutputListener(output -> {
114+
try {
115+
String serialized = output.toString();
116+
if (actual.contains(serialized)) {
117+
duplicated.compareAndSet(false, true);
118+
} else {
119+
actual.add(serialized);
120+
}
121+
} catch (Exception e) {
122+
e.printStackTrace();
123+
exceptional.compareAndSet(false, true);
124+
}
125+
});
126+
127+
bulkCaller.awaitCompletion();
128+
assertEquals("exceptions on calls", false, exceptional.get());
129+
assertEquals("duplicate output", false, duplicated.get());
130+
assertEquals("unexpected output count", expected.size(), actual.size());
131+
}
132+
92133
@AfterClass
93134
public static void cleanup() {
94135

0 commit comments

Comments
 (0)