diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputCaller.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputCaller.java index 00c1ab725..76e9649ad 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputCaller.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputCaller.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.marklogic.client.dataservices; import com.marklogic.client.DatabaseClient; +import com.marklogic.client.dataservices.impl.HandleProvider; import com.marklogic.client.dataservices.impl.InputEndpointImpl; import com.marklogic.client.io.marker.BufferableContentHandle; import com.marklogic.client.io.marker.BufferableHandle; @@ -37,7 +38,7 @@ public interface InputCaller extends IOEndpoint { * @return the InputCaller instance for calling the endpoint. */ static InputCaller on(DatabaseClient client, JSONWriteHandle apiDecl, BufferableContentHandle inputHandle) { - return new InputEndpointImpl(client, apiDecl, inputHandle); + return new InputEndpointImpl(client, apiDecl, new HandleProvider.ContentHandleProvider<>(inputHandle,null)); } /** diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputEndpoint.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputEndpoint.java index 6dedb7a3e..1aa96cd02 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputEndpoint.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.marklogic.client.DatabaseClient; import com.marklogic.client.SessionState; +import com.marklogic.client.dataservices.impl.HandleProvider; import com.marklogic.client.dataservices.impl.InputEndpointImpl; import com.marklogic.client.io.InputStreamHandle; import com.marklogic.client.io.marker.JSONWriteHandle; @@ -39,7 +40,7 @@ static InputEndpoint on(DatabaseClient client, JSONWriteHandle apiDecl) { final class EndpointLocal extends InputEndpointImpl implements InputEndpoint { private EndpointLocal(DatabaseClient client, JSONWriteHandle apiDecl) { - super(client, apiDecl, new InputStreamHandle()); + super(client, apiDecl, new HandleProvider.ContentHandleProvider<>(new InputStreamHandle(), null)); } public InputEndpoint.BulkInputCaller bulkCaller() { return new BulkLocal(this); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputCaller.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputCaller.java index 0d8e86a7f..1101cded9 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputCaller.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputCaller.java @@ -16,6 +16,7 @@ package com.marklogic.client.dataservices; import com.marklogic.client.DatabaseClient; +import com.marklogic.client.dataservices.impl.HandleProvider; import com.marklogic.client.dataservices.impl.InputOutputEndpointImpl; import com.marklogic.client.io.marker.BufferableContentHandle; import com.marklogic.client.io.marker.BufferableHandle; @@ -44,9 +45,9 @@ public interface InputOutputCaller extends IOEndpoint { */ static InputOutputCaller on( DatabaseClient client, JSONWriteHandle apiDecl, - BufferableContentHandle inputHandle, BufferableContentHandle outputHandle + BufferableContentHandle inputHandle, BufferableContentHandle outputHandle ) { - return new InputOutputEndpointImpl(client, apiDecl, inputHandle, outputHandle); + return new InputOutputEndpointImpl<>(client, apiDecl, new HandleProvider.ContentHandleProvider<>(inputHandle, outputHandle)); } /** diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputEndpoint.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputEndpoint.java index 34d38fff8..e5660dc0c 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputEndpoint.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/InputOutputEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import com.marklogic.client.DatabaseClient; import com.marklogic.client.SessionState; +import com.marklogic.client.dataservices.impl.HandleProvider; import com.marklogic.client.dataservices.impl.InputOutputEndpointImpl; import com.marklogic.client.io.InputStreamHandle; import com.marklogic.client.io.marker.JSONWriteHandle; @@ -40,7 +41,7 @@ static InputOutputEndpoint on(DatabaseClient client, JSONWriteHandle apiDecl) { final class EndpointLocal extends InputOutputEndpointImpl implements InputOutputEndpoint { private EndpointLocal(DatabaseClient client, JSONWriteHandle apiDecl) { - super(client, apiDecl, new InputStreamHandle(), new InputStreamHandle()); + super(client, apiDecl, new HandleProvider.ContentHandleProvider<>(new InputStreamHandle(), new InputStreamHandle())); } public InputOutputEndpoint.BulkInputOutputCaller bulkCaller() { return new BulkLocal(this); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputCaller.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputCaller.java index 02be0212c..cdf501e36 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputCaller.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputCaller.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.marklogic.client.dataservices; import com.marklogic.client.DatabaseClient; +import com.marklogic.client.dataservices.impl.HandleProvider; import com.marklogic.client.dataservices.impl.OutputEndpointImpl; import com.marklogic.client.io.marker.BufferableContentHandle; import com.marklogic.client.io.marker.JSONWriteHandle; @@ -40,7 +41,7 @@ public interface OutputCaller extends IOEndpoint { static OutputCaller on( DatabaseClient client, JSONWriteHandle apiDecl, BufferableContentHandle outputHandle ) { - return new OutputEndpointImpl(client, apiDecl, outputHandle); + return new OutputEndpointImpl(client, apiDecl, new HandleProvider.ContentHandleProvider<>(null, outputHandle)); } /** diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputEndpoint.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputEndpoint.java index 517d7c290..476ebfaf4 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputEndpoint.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/OutputEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import com.marklogic.client.DatabaseClient; import com.marklogic.client.SessionState; +import com.marklogic.client.dataservices.impl.HandleProvider; import com.marklogic.client.dataservices.impl.OutputEndpointImpl; import com.marklogic.client.io.InputStreamHandle; import com.marklogic.client.io.marker.JSONWriteHandle; @@ -39,7 +40,7 @@ static OutputEndpoint on(DatabaseClient client, JSONWriteHandle apiDecl) { final class EndpointLocal extends OutputEndpointImpl implements OutputEndpoint { private EndpointLocal(DatabaseClient client, JSONWriteHandle apiDecl) { - super(client, apiDecl, new InputStreamHandle()); + super(client, apiDecl, new HandleProvider.ContentHandleProvider<>(null, new InputStreamHandle())); } public OutputEndpoint.BulkOutputCaller bulkCaller() { return new BulkLocal(this); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecCallerImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecCallerImpl.java index 4b7bd342e..1b69f0a9b 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecCallerImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecCallerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ final public class ExecCallerImpl extends IOCallerImpl { public ExecCallerImpl(JSONWriteHandle apiDeclaration) { - super(apiDeclaration, null, null); + super(apiDeclaration, new HandleProvider.ContentHandleProvider<>(null, null)); if (getInputParamdef() != null) { throw new IllegalArgumentException("input parameter not supported in endpoint: "+ getEndpointPath()); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecEndpointImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecEndpointImpl.java index ced4365fa..46218a9fd 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecEndpointImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/ExecEndpointImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -127,7 +127,7 @@ public void awaitCompletion() { else if(getCallContextQueue() != null && !getCallContextQueue().isEmpty()){ try { for (int i = 0; i < getThreadCount(); i++) { - BulkCallableImpl bulkCallableImpl = new BulkCallableImpl(this); + BulkCallableImpl bulkCallableImpl = new BulkCallableImpl(this); submitTask(bulkCallableImpl); } if(getCallerThreadPoolExecutor() != null) @@ -235,8 +235,7 @@ private boolean processOutput(CallContextImpl callContext){ } } -// TODO: static private class BulkCallableImpl - private class BulkCallableImpl implements Callable { + static private class BulkCallableImpl implements Callable { private final BulkExecCallerImpl bulkExecCallerImpl; BulkCallableImpl(BulkExecCallerImpl bulkExecCallerImpl) { @@ -251,11 +250,11 @@ public Boolean call() throws InterruptedException{ if(continueCalling) { bulkExecCallerImpl.getCallContextQueue().put(callContext); - submitTask(this); + bulkExecCallerImpl.submitTask(this); } else { - if (aliveCallContextCount.decrementAndGet() == 0 && getCallerThreadPoolExecutor() != null) { - getCallerThreadPoolExecutor().shutdown(); + if (bulkExecCallerImpl.aliveCallContextCount.decrementAndGet() == 0 && bulkExecCallerImpl.getCallerThreadPoolExecutor() != null) { + bulkExecCallerImpl.getCallerThreadPoolExecutor().shutdown(); } } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/HandleProvider.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/HandleProvider.java new file mode 100644 index 000000000..300ce5ada --- /dev/null +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/HandleProvider.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2021 MarkLogic Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.marklogic.client.dataservices.impl; + +import com.marklogic.client.impl.RESTServices; +import com.marklogic.client.io.marker.BufferableContentHandle; + +public interface HandleProvider { + BufferableContentHandle getInputHandle(); + BufferableContentHandle getOutputHandle(); + I[] newInputArray(int length); + O[] newOutputArray(int length); + BufferableContentHandle[] bufferableInputHandleOn(I[] input); + O[] outputAsArray(CallContextImpl callCtxt, RESTServices.MultipleCallResponse response); + + class ContentHandleProvider implements HandleProvider { + private final BufferableContentHandle inputHandle; + private final BufferableContentHandle outputHandle; + public ContentHandleProvider(BufferableContentHandle inputHandle, BufferableContentHandle outputHandle) { + this.inputHandle = inputHandle; + this.outputHandle = outputHandle; + } + @Override + public BufferableContentHandle getInputHandle() { + return inputHandle; + } + @Override + public BufferableContentHandle getOutputHandle() { + return outputHandle; + } + @Override + public I[] newInputArray(int length) { + if (inputHandle == null) { + throw new IllegalStateException("No input handle provided"); + } + return inputHandle.newArray(length); + } + @Override + public O[] newOutputArray(int length) { + if (outputHandle == null) { + throw new IllegalStateException("No output handle provided"); + } + return outputHandle.newArray(length); + } + @Override + public BufferableContentHandle[] bufferableInputHandleOn(I[] input) { + if (inputHandle == null) { + throw new IllegalStateException("No input handle provided"); + } + return inputHandle.resendableHandleFor(input); + } + @Override + public O[] outputAsArray(CallContextImpl callCtxt, RESTServices.MultipleCallResponse response) { + if (outputHandle == null) { + throw new IllegalStateException("No output handle provided"); + } + return response.asArrayOfContent( + callCtxt.isLegacyContext() ? null : callCtxt.getEndpointState(), outputHandle + ); + } + } + + class DirectHandleProvider + implements HandleProvider, BufferableContentHandle> { + private final BufferableContentHandle inputHandle; + private final BufferableContentHandle outputHandle; + public DirectHandleProvider(BufferableContentHandle inputHandle, BufferableContentHandle outputHandle) { + this.inputHandle = inputHandle; + this.outputHandle = outputHandle; + } + @Override + public BufferableContentHandle getInputHandle() { + return inputHandle; + } + @Override + public BufferableContentHandle getOutputHandle() { + return outputHandle; + } + @Override + public BufferableContentHandle[] newInputArray(int length) { + if (inputHandle == null) { + throw new IllegalStateException("No input handle provided"); + } + return inputHandle.newHandleArray(length); + } + @Override + public BufferableContentHandle[] newOutputArray(int length) { + if (outputHandle == null) { + throw new IllegalStateException("No output handle provided"); + } + return outputHandle.newHandleArray(length); + } + @Override + public BufferableContentHandle[] bufferableInputHandleOn(BufferableContentHandle[] input) { + return input; + } + @Override + public BufferableContentHandle[] outputAsArray( + CallContextImpl, BufferableContentHandle> callCtxt, + RESTServices.MultipleCallResponse response) { + if (outputHandle == null) { + throw new IllegalStateException("No output handle provided"); + } + return response.asArrayOfHandles(callCtxt.getEndpointState(), outputHandle); + } + } +} diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOCallerImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOCallerImpl.java index 0f7c62284..e0a6e2341 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOCallerImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOCallerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,55 +24,55 @@ import com.marklogic.client.io.BaseHandle; import com.marklogic.client.io.BytesHandle; import com.marklogic.client.io.marker.BufferableContentHandle; -import com.marklogic.client.io.marker.BufferableHandle; import com.marklogic.client.io.marker.JSONWriteHandle; -import java.util.stream.Stream; - abstract class IOCallerImpl extends BaseCallerImpl { private final JsonNode apiDeclaration; private final String endpointPath; private final BaseProxy.DBFunctionRequest requester; - private BufferableContentHandle inputHandle; - private BufferableContentHandle outputHandle; - private ParamdefImpl endpointStateParamdef; private ParamdefImpl sessionParamdef; private ParamdefImpl endpointConstantsParamdef; private ParamdefImpl inputParamdef; private ReturndefImpl returndef; - IOCallerImpl( - JSONWriteHandle apiDeclaration, BufferableContentHandle inputHandle, BufferableContentHandle outputHandle - ) { - super(); + private final HandleProvider handleProvider; - if (apiDeclaration== null) { + IOCallerImpl(JSONWriteHandle apiDeclaration, HandleProvider handleProvider) { + super(); + if (apiDeclaration == null) { throw new IllegalArgumentException("null endpoint declaration"); + } else if (handleProvider == null) { + throw new IllegalArgumentException("null handle provider"); } this.apiDeclaration = NodeConverter.handleToJsonNode(apiDeclaration); if (!this.apiDeclaration.isObject()) { throw new IllegalArgumentException( - "endpoint declaration must be object: " + this.apiDeclaration.toString() + "endpoint declaration must be object: " + this.apiDeclaration ); } + this.handleProvider = handleProvider; + this.endpointPath = getText(this.apiDeclaration.get("endpoint")); if (this.endpointPath == null || this.endpointPath.length() == 0) { throw new IllegalArgumentException( - "no endpoint in endpoint declaration: " + this.apiDeclaration.toString() + "no endpoint in endpoint declaration: " + this.apiDeclaration ); } int nodeArgCount = 0; + BaseHandle inputHandleBase = (BaseHandle) handleProvider.getInputHandle(); + BaseHandle outputHandleBase = (BaseHandle) handleProvider.getOutputHandle(); + JsonNode functionParams = this.apiDeclaration.get("params"); if (functionParams != null) { if (!functionParams.isArray()) { throw new IllegalArgumentException( - "params must be array in endpoint declaration: " + this.apiDeclaration.toString() + "params must be array in endpoint declaration: " + this.apiDeclaration ); } @@ -81,7 +81,7 @@ abstract class IOCallerImpl extends BaseCallerImpl { for (JsonNode functionParam : functionParams) { if (!functionParam.isObject()) { throw new IllegalArgumentException( - "parameter must be object in endpoint declaration: " + functionParam.toString() + "parameter must be object in endpoint declaration: " + functionParam ); } ParamdefImpl paramdef = new ParamdefImpl(functionParam); @@ -104,11 +104,10 @@ abstract class IOCallerImpl extends BaseCallerImpl { throw new IllegalArgumentException("input parameter must be nullable"); } this.inputParamdef = paramdef; - if (inputHandle == null) { + if (inputHandleBase == null) { throw new IllegalArgumentException("no input handle provided for input parameter"); } - ((BaseHandle) inputHandle).setFormat(paramdef.getFormat()); - this.inputHandle = inputHandle; + inputHandleBase.setFormat(paramdef.getFormat()); nodeArgCount += 2; break; case "session": @@ -136,7 +135,7 @@ abstract class IOCallerImpl extends BaseCallerImpl { } } } - if (this.inputParamdef == null && inputHandle != null) { + if (this.inputParamdef == null && inputHandleBase != null) { throw new IllegalArgumentException("no input parameter declared but input handle provided"); } @@ -144,20 +143,19 @@ abstract class IOCallerImpl extends BaseCallerImpl { if (functionReturn != null) { if (!functionReturn.isObject()) { throw new IllegalArgumentException( - "return must be object in endpoint declaration: "+functionReturn.toString() + "return must be object in endpoint declaration: "+ functionReturn ); } this.returndef = new ReturndefImpl(functionReturn); if (!this.returndef.isNullable()) { throw new IllegalArgumentException("return must be nullable"); } - if (outputHandle != null) { - ((BaseHandle) outputHandle).setFormat(this.returndef.getFormat()); - this.outputHandle = outputHandle; + if (outputHandleBase != null) { + outputHandleBase.setFormat(this.returndef.getFormat()); } else if (this.endpointStateParamdef == null) { throw new IllegalArgumentException("no output handle provided for return values"); } - } else if (outputHandle != null) { + } else if (outputHandleBase != null) { throw new IllegalArgumentException("no return values declared but output handle provided"); } @@ -178,18 +176,26 @@ abstract class IOCallerImpl extends BaseCallerImpl { ); } - BufferableContentHandle getInputHandle() { - return inputHandle; + BufferableContentHandle[] bufferableInputHandleOn(I[] input) { + return handleProvider.bufferableInputHandleOn(input); } - BufferableContentHandle getOutputHandle() { - return outputHandle; + I[] newContentInputArray(int length) { + return handleProvider.newInputArray(length); + } + O[] newContentOutputArray(int length) { + return handleProvider.newOutputArray(length); } BaseProxy.DBFunctionRequest makeRequest(DatabaseClient db, CallContextImpl callCtxt) { return makeRequest(db, callCtxt, (RESTServices.CallField) null); } BaseProxy.DBFunctionRequest makeRequest( - DatabaseClient db, CallContextImpl callCtxt, BufferableHandle[] input + DatabaseClient db, CallContextImpl callCtxt, I[] input + ) { + return makeRequest(db, callCtxt, bufferableInputHandleOn(input)); + } + private BaseProxy.DBFunctionRequest makeRequest( + DatabaseClient db, CallContextImpl callCtxt, BufferableContentHandle[] input ) { RESTServices.CallField inputField = null; @@ -279,17 +285,8 @@ boolean responseWithState(BaseProxy.DBFunctionRequest request, CallContextImpl callCtxt) { - return responseMultiple(request).asArrayOfContent(callCtxt.isLegacyContext() ? null : callCtxt.getEndpointState(), outputHandle); + return handleProvider.outputAsArray(callCtxt, responseMultiple(request)); } private RESTServices.MultipleCallResponse responseMultiple(BaseProxy.DBFunctionRequest request) { if (getReturndef() == null) { diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOEndpointImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOEndpointImpl.java index 77042abe8..ad47d47a4 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOEndpointImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/IOEndpointImpl.java @@ -323,7 +323,7 @@ boolean checkQueue(BlockingQueue queue, int batchSize) { I[] getInputBatch(BlockingQueue queue, int batchSize) { List inputStreamList = new ArrayList<>(); queue.drainTo(inputStreamList, batchSize); - return inputStreamList.toArray(endpoint.getCaller().getInputHandle().newArray(inputStreamList.size())); + return inputStreamList.toArray(endpoint.getCaller().newContentInputArray(inputStreamList.size())); } void processOutputBatch(O[] output, Consumer outputListener) { if (output == null || output.length == 0) return; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputCallerImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputCallerImpl.java index e1fe078de..f119405d4 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputCallerImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputCallerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,12 @@ */ package com.marklogic.client.dataservices.impl; -import java.util.stream.Stream; - import com.marklogic.client.DatabaseClient; -import com.marklogic.client.io.marker.BufferableContentHandle; -import com.marklogic.client.io.marker.BufferableHandle; import com.marklogic.client.io.marker.JSONWriteHandle; final public class InputCallerImpl extends IOCallerImpl { - public InputCallerImpl(JSONWriteHandle apiDeclaration, BufferableContentHandle inputHandle) { - super(apiDeclaration, inputHandle, null); + public InputCallerImpl(JSONWriteHandle apiDeclaration, HandleProvider handleProvider) { + super(apiDeclaration, handleProvider); if (getInputParamdef() == null) { throw new IllegalArgumentException("input parameter missing in endpoint: "+ getEndpointPath()); @@ -42,7 +38,7 @@ public InputCallerImpl(JSONWriteHandle apiDeclaration, BufferableContentHandle callCtxt, BufferableHandle[] input) { + public void arrayCall(DatabaseClient db, CallContextImpl callCtxt, I[] input) { responseWithState(makeRequest(db, callCtxt, input), callCtxt); } } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputEndpointImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputEndpointImpl.java index 675aa68fa..e7b0bab8b 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputEndpointImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputEndpointImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,6 @@ import java.util.concurrent.LinkedBlockingQueue; import com.marklogic.client.dataservices.InputCaller; -import com.marklogic.client.io.marker.BufferableContentHandle; -import com.marklogic.client.io.marker.BufferableHandle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,8 +33,10 @@ public class InputEndpointImpl extends IOEndpointImpl implements Input private final InputCallerImpl caller; private final int batchSize; - public InputEndpointImpl(DatabaseClient client, JSONWriteHandle apiDecl, BufferableContentHandle inputHandle) { - this(client, new InputCallerImpl<>(apiDecl, inputHandle)); + public InputEndpointImpl( + DatabaseClient client, JSONWriteHandle apiDecl, HandleProvider handleProvider + ) { + this(client, new InputCallerImpl<>(apiDecl, handleProvider)); } private InputEndpointImpl(DatabaseClient client, InputCallerImpl caller) { super(client, caller); @@ -58,7 +58,7 @@ public void call(I[] input) { @Override public void call(CallContext callContext, I[] input) { InputCallerImpl callerImpl = getCaller(); - callerImpl.arrayCall(getClient(), checkAllowedArgs(callContext), callerImpl.getInputHandle().resendableHandleFor(input)); + callerImpl.arrayCall(getClient(), checkAllowedArgs(callContext), input); } @Deprecated @@ -196,11 +196,10 @@ private void processInput(CallContextImpl callContext, I[] inputBatch) { ErrorDisposition error = ErrorDisposition.RETRY; - BufferableHandle[] inputHandles = callerImpl.getInputHandle().resendableHandleFor(inputBatch); for (int retryCount = 0; retryCount < DEFAULT_MAX_RETRIES && error == ErrorDisposition.RETRY; retryCount++) { Throwable throwable = null; try { - getEndpoint().getCaller().arrayCall(callContext.getClient(), callContext, inputHandles); + getEndpoint().getCaller().arrayCall(callContext.getClient(), callContext, inputBatch); incrementCallCount(); return; } catch (Throwable catchedThrowable) { @@ -215,7 +214,9 @@ private void processInput(CallContextImpl callContext, I[] inputBatch) { try { if (retryCount < DEFAULT_MAX_RETRIES - 1) { - error = getErrorListener().processError(retryCount, throwable, callContext, inputHandles); + error = getErrorListener().processError( + retryCount, throwable, callContext, callerImpl.bufferableInputHandleOn(inputBatch) + ); } else { error = ErrorDisposition.SKIP_CALL; } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputCallerImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputCallerImpl.java index 4638e70ae..330982d12 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputCallerImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputCallerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,12 @@ */ package com.marklogic.client.dataservices.impl; -import java.util.stream.Stream; - import com.marklogic.client.DatabaseClient; -import com.marklogic.client.io.marker.BufferableContentHandle; -import com.marklogic.client.io.marker.BufferableHandle; import com.marklogic.client.io.marker.JSONWriteHandle; final public class InputOutputCallerImpl extends IOCallerImpl { - public InputOutputCallerImpl( - JSONWriteHandle apiDeclaration, BufferableContentHandle inputHandle, BufferableContentHandle outputHandle - ) { - super(apiDeclaration, inputHandle, outputHandle); + public InputOutputCallerImpl(JSONWriteHandle apiDeclaration, HandleProvider handleProvider) { + super(apiDeclaration, handleProvider); if (getInputParamdef() == null) { throw new IllegalArgumentException("input parameter missing in endpoint: "+ getEndpointPath()); @@ -40,7 +34,7 @@ public InputOutputCallerImpl( } } - public O[] arrayCall(DatabaseClient db, CallContextImpl callCtxt, BufferableHandle[] input) { + public O[] arrayCall(DatabaseClient db, CallContextImpl callCtxt, I[] input) { return responseMultipleAsArray(makeRequest(db, callCtxt, input), callCtxt); } } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputEndpointImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputEndpointImpl.java index ce1e708c2..97016f06b 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputEndpointImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/InputOutputEndpointImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,6 @@ import com.marklogic.client.DatabaseClient; import com.marklogic.client.SessionState; import com.marklogic.client.dataservices.InputOutputCaller; -import com.marklogic.client.io.marker.BufferableContentHandle; -import com.marklogic.client.io.marker.BufferableHandle; import com.marklogic.client.io.marker.JSONWriteHandle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,10 +33,9 @@ public class InputOutputEndpointImpl extends IOEndpointImpl implements private final int batchSize; public InputOutputEndpointImpl( - DatabaseClient client, JSONWriteHandle apiDecl, - BufferableContentHandle inputHandle, BufferableContentHandle outputHandle + DatabaseClient client, JSONWriteHandle apiDecl, HandleProvider handleProvider ) { - this(client, new InputOutputCallerImpl<>(apiDecl, inputHandle, outputHandle)); + this(client, new InputOutputCallerImpl<>(apiDecl, handleProvider)); } private InputOutputEndpointImpl(DatabaseClient client, InputOutputCallerImpl caller) { super(client, caller); @@ -103,7 +100,7 @@ public BulkInputOutputCaller bulkCaller(CallContext[] callContexts, int thr private O[] getResponseData(CallContext callContext, I[] input) { InputOutputCallerImpl callerImpl = getCaller(); return callerImpl.arrayCall( - getClient(), checkAllowedArgs(callContext), callerImpl.getInputHandle().resendableHandleFor(input) + getClient(), checkAllowedArgs(callContext), input ); } @@ -201,12 +198,11 @@ private void processInput(CallContextImpl callContext, I[] inputBatch) { ErrorDisposition error = ErrorDisposition.RETRY; - BufferableHandle[] inputHandles = callerImpl.getInputHandle().resendableHandleFor(inputBatch); for (int retryCount = 0; retryCount < DEFAULT_MAX_RETRIES && error == ErrorDisposition.RETRY; retryCount++) { Throwable throwable = null; O[] output = null; try { - output = callerImpl.arrayCall(callContext.getClient(), callContext, inputHandles); + output = callerImpl.arrayCall(callContext.getClient(), callContext, inputBatch); incrementCallCount(); processOutputBatch(output, getOutputListener()); @@ -219,7 +215,9 @@ private void processInput(CallContextImpl callContext, I[] inputBatch) { if (getErrorListener() != null) { try { if (retryCount < DEFAULT_MAX_RETRIES - 1) { - error = getErrorListener().processError(retryCount, throwable, callContext, inputHandles); + error = getErrorListener().processError( + retryCount, throwable, callContext, callerImpl.bufferableInputHandleOn(inputBatch) + ); } else { error = ErrorDisposition.SKIP_CALL; } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputCallerImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputCallerImpl.java index c1b0a114b..23a7a776d 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputCallerImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputCallerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,12 @@ */ package com.marklogic.client.dataservices.impl; -import java.util.stream.Stream; - import com.marklogic.client.DatabaseClient; -import com.marklogic.client.io.marker.BufferableContentHandle; import com.marklogic.client.io.marker.JSONWriteHandle; final public class OutputCallerImpl extends IOCallerImpl { - public OutputCallerImpl(JSONWriteHandle apiDeclaration, BufferableContentHandle outputHandle) { - super(apiDeclaration, null, outputHandle); + public OutputCallerImpl(JSONWriteHandle apiDeclaration, HandleProvider handleProvider) { + super(apiDeclaration, handleProvider); if (getInputParamdef() != null) { throw new IllegalArgumentException("input parameter not supported in endpoint: "+ getEndpointPath()); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputEndpointImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputEndpointImpl.java index f1b952708..6b1f7e98e 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputEndpointImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/dataservices/impl/OutputEndpointImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import com.marklogic.client.MarkLogicInternalException; import com.marklogic.client.SessionState; import com.marklogic.client.dataservices.OutputCaller; -import com.marklogic.client.io.marker.BufferableContentHandle; import com.marklogic.client.io.marker.JSONWriteHandle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,8 +32,10 @@ public class OutputEndpointImpl extends IOEndpointImpl implements Outp private static final Logger logger = LoggerFactory.getLogger(OutputEndpointImpl.class); private final OutputCallerImpl caller; - public OutputEndpointImpl(DatabaseClient client, JSONWriteHandle apiDecl, BufferableContentHandle outputHandle) { - this(client, new OutputCallerImpl<>(apiDecl, outputHandle)); + public OutputEndpointImpl( + DatabaseClient client, JSONWriteHandle apiDecl, HandleProvider handleProvider + ) { + this(client, new OutputCallerImpl<>(apiDecl, handleProvider)); } private OutputEndpointImpl(DatabaseClient client, OutputCallerImpl caller) { super(client, caller); @@ -165,7 +166,7 @@ public void awaitCompletion() { else if(getCallContextQueue() != null && !getCallContextQueue().isEmpty()){ try { for (int i = 0; i < getThreadCount(); i++) { - BulkCallableImpl bulkCallableImpl = new BulkCallableImpl(this); + BulkCallableImpl bulkCallableImpl = new BulkCallableImpl(this); submitTask(bulkCallableImpl); } getCallerThreadPoolExecutor().awaitTermination(); @@ -218,7 +219,7 @@ private O[] getOutputStream(CallContextImpl callContext) { if(callContext.getEndpoint().allowsEndpointState()) { callContext.withEndpointState(null); } - return getEndpoint().getCaller().getOutputHandle().newArray(0); + return getEndpoint().getCaller().newContentOutputArray(0); case STOP_ALL_CALLS: if (getCallerThreadPoolExecutor() != null) { @@ -229,7 +230,7 @@ private O[] getOutputStream(CallContextImpl callContext) { } } - return (output == null) ? getEndpoint().getCaller().getOutputHandle().newArray(0) : output; + return (output == null) ? getEndpoint().getCaller().newContentOutputArray(0) : output; } private void processOutput() { @@ -274,8 +275,7 @@ private boolean processOutput(CallContextImpl callContext){ } -// TODO: make static private class BulkCallableImpl - private class BulkCallableImpl implements Callable { + static private class BulkCallableImpl implements Callable { private final BulkOutputCallerImpl bulkOutputCallerImpl; BulkCallableImpl(BulkOutputCallerImpl bulkOutputCallerImpl) { @@ -289,10 +289,10 @@ public Boolean call() { boolean continueCalling = (callContext == null) ? false : bulkOutputCallerImpl.processOutput(callContext); if (continueCalling) { bulkOutputCallerImpl.getCallContextQueue().put(callContext); - submitTask(this); + bulkOutputCallerImpl.submitTask(this); } else { - if (getCallerThreadPoolExecutor() != null && aliveCallContextCount.decrementAndGet() == 0) { - getCallerThreadPoolExecutor().shutdown(); + if (bulkOutputCallerImpl.getCallerThreadPoolExecutor() != null && bulkOutputCallerImpl.aliveCallContextCount.decrementAndGet() == 0) { + bulkOutputCallerImpl.getCallerThreadPoolExecutor().shutdown(); } } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/extra/dom4j/DOM4JHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/extra/dom4j/DOM4JHandle.java index 57ec4697d..c9f7af000 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/extra/dom4j/DOM4JHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/extra/dom4j/DOM4JHandle.java @@ -95,6 +95,11 @@ public DOM4JHandle(Document content) { public DOM4JHandle newHandle() { return new DOM4JHandle(); } + @Override + public DOM4JHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new DOM4JHandle[length]; + } /** * Returns the dom4j reader for XML content. diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/extra/gson/GSONHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/extra/gson/GSONHandle.java index 415805608..dd25e4f38 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/extra/gson/GSONHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/extra/gson/GSONHandle.java @@ -89,6 +89,11 @@ public GSONHandle(JsonElement content) { public GSONHandle newHandle() { return new GSONHandle(); } + @Override + public GSONHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new GSONHandle[length]; + } /** * Returns the parser used to construct element objects from JSON. diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/extra/jdom/JDOMHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/extra/jdom/JDOMHandle.java index 211757cc3..f8a9a22d4 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/extra/jdom/JDOMHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/extra/jdom/JDOMHandle.java @@ -95,6 +95,12 @@ public JDOMHandle newHandle() { return new JDOMHandle(); } + @Override + public JDOMHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new JDOMHandle[length]; + } + /** * Returns the JDOM structure builder for XML content. * diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/BaseProxy.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/BaseProxy.java index 8d67679d4..f39d113b1 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/BaseProxy.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/BaseProxy.java @@ -789,13 +789,15 @@ static public SingleAtomicCallField atomicParam(String paramName, boolean isNull static public MultipleAtomicCallField atomicParam(String paramName, boolean isNullable, Stream values) { return isParamNull(paramName, isNullable, values) ? null : new UnbufferedMultipleAtomicCallField(paramName, values); } - static public SingleNodeCallField documentParam(String paramName, boolean isNullable, BufferableHandle value) { + static public SingleNodeCallField documentParam(String paramName, boolean isNullable, BufferableContentHandle value) { return isParamNull(paramName, isNullable, value) ? null : new SingleNodeCallField(paramName, value); } - static public MultipleNodeCallField documentParam(String paramName, boolean isNullable, Stream values) { + static public MultipleNodeCallField documentParam( + String paramName, boolean isNullable, Stream> values + ) { return isParamNull(paramName, isNullable, values) ? null : new UnbufferedMultipleNodeCallField(paramName, values); } - static public MultipleNodeCallField documentParam(String paramName, boolean isNullable, BufferableHandle[] values) { + static public MultipleNodeCallField documentParam(String paramName, boolean isNullable, BufferableContentHandle[] values) { return isParamNull(paramName, isNullable, values) ? null : new BufferedMultipleNodeCallField(paramName, values); } static protected boolean isParamNull(String paramName, boolean isNullable, Object value) { diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java index a96a4bb55..ee6e7b369 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java @@ -1908,9 +1908,13 @@ static private void updateFormat(ContentDescriptor descriptor, Format format) { static private Format getHeaderFormat(Headers headers) { String format = headers.get(HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT); - if (format != null) { + if (format != null && format.length() > 0) { return Format.valueOf(format.toUpperCase()); } + String contentType = headers.get(HEADER_CONTENT_TYPE); + if (contentType != null && contentType.length() > 0) { + return Format.getFromMimetype(contentType); + } return null; } @@ -2046,6 +2050,27 @@ static private Request.Builder addVersionHeader(DocumentDescriptor desc, Request return requestBldr; } + static private R updateHandle(BodyPart part, R handle) { + HandleImplementation handleBase = HandleAccessor.as(handle); + + updateFormat(handleBase, getHeaderFormat(part)); + updateMimetype(handleBase, getHeaderMimetype(OkHttpServices.getHeader(part, HEADER_CONTENT_TYPE))); + updateLength(handleBase, getHeaderLength(OkHttpServices.getHeader(part, HEADER_CONTENT_LENGTH))); + handleBase.receiveContent(getEntity(part, handleBase.receiveAs())); + + return handle; + } + static private R updateHandle(Headers headers, ResponseBody body, R handle) { + HandleImplementation handleBase = HandleAccessor.as(handle); + + updateFormat(handleBase, getHeaderFormat(headers)); + updateMimetype(handleBase, getHeaderMimetype(headers.get(HEADER_CONTENT_TYPE))); + updateLength(handleBase, getHeaderLength(headers.get(HEADER_CONTENT_LENGTH))); + handleBase.receiveContent(getEntity(body, handleBase.receiveAs())); + + return handle; + } + @Override public T search(RequestLogger reqlog, T searchHandle, SearchQueryDefinition queryDef, long start, long len, QueryView view, @@ -5424,8 +5449,9 @@ static private String getReasonPhrase(Response response) { static private T getEntity(BodyPart part, Class as) { try { String contentType = part.getContentType(); - return getEntity(ResponseBody.create(MediaType.parse(contentType), part.getSize(), - Okio.buffer(Okio.source(part.getInputStream()))), as); + return getEntity( + ResponseBody.create(Okio.buffer(Okio.source(part.getInputStream())), MediaType.parse(contentType), part.getSize()), + as); } catch (IOException e) { throw new MarkLogicIOException(e); } catch (MessagingException e) { @@ -5976,6 +6002,9 @@ static class CallResponseImpl implements CallResponse { private boolean isNull = true; private Response response; + Response getResponse() { + return response; + } void setResponse(Response response) { this.response = response; } @@ -6063,12 +6092,19 @@ public C asContent(BufferableContentHandle outputHandle) { return content; } @Override + public > T asHandle(T outputHandle) { + if (responseBody == null) return null; + + return updateHandle(getResponse().headers(), responseBody, outputHandle); + } + @Override public InputStream asInputStream() { return (responseBody == null) ? null : responseBody.byteStream(); } @Override public InputStreamHandle asInputStreamHandle() { - return (responseBody == null) ? null : new InputStreamHandle(asInputStream()); + return (responseBody == null) ? null : + updateHandle(getResponse().headers(), responseBody, new InputStreamHandle()); } @Override public Reader asReader() { @@ -6076,7 +6112,8 @@ public Reader asReader() { } @Override public ReaderHandle asReaderHandle() { - return (responseBody == null) ? null : new ReaderHandle(asReader()); + return (responseBody == null) ? null : + updateHandle(getResponse().headers(), responseBody, new ReaderHandle(asReader())); } @Override public String asString() { @@ -6169,9 +6206,9 @@ public Stream asStreamOfBytes() { } return builder.build(); } catch (MessagingException e) { - throw new RuntimeException(e); + throw new MarkLogicIOException(e); } catch (IOException e) { - throw new RuntimeException(e); + throw new MarkLogicIOException(e); } } @Override @@ -6191,10 +6228,11 @@ public Stream asStreamOfContent( Stream.Builder builder = Stream.builder(); for (int i=0; i < partCount; i++) { - C value = responsePartToContent(outputHandle, multipart.getBodyPart(i), receiveClass); + BodyPart bodyPart = multipart.getBodyPart(i); if (hasEndpointState && i == 0) { - endpointStateHandle.set(outputHandle.contentToBytes(value)); + updateHandle(bodyPart, endpointStateHandle); } else { + C value = responsePartToContent(outputHandle, bodyPart, receiveClass); builder.accept(value); } } @@ -6207,6 +6245,35 @@ public Stream asStreamOfContent( } } @Override + public > Stream asStreamOfHandles( + BytesHandle endpointStateHandle, T outputHandle + ) { + try { + if (multipart == null) { + return Stream.empty(); + } + + boolean hasEndpointState = (endpointStateHandle != null); + + Stream.Builder builder = Stream.builder(); + + int partCount = multipart.getCount(); + for (int i=0; i < partCount; i++) { + BodyPart bodyPart = multipart.getBodyPart(i); + if (hasEndpointState && i == 0) { + updateHandle(bodyPart, endpointStateHandle); + } else { + builder.accept(updateHandle(bodyPart, (T) outputHandle.newHandle())); + } + } + return builder.build(); + } catch (MessagingException e) { + throw new MarkLogicIOException(e); + } finally { + outputHandle.set(null); + } + } + @Override public Stream asStreamOfInputStreamHandle() { try { if (multipart == null) { @@ -6217,13 +6284,11 @@ public Stream asStreamOfInputStreamHandle() { Stream.Builder builder = Stream.builder(); for (int i=0; i < partCount; i++) { BodyPart bodyPart = multipart.getBodyPart(i); - builder.accept(new InputStreamHandle(bodyPart.getInputStream())); + builder.accept(updateHandle(bodyPart, new InputStreamHandle())); } return builder.build(); } catch (MessagingException e) { throw new MarkLogicIOException(e); - } catch (IOException e) { - throw new MarkLogicIOException(e); } } @Override @@ -6277,13 +6342,11 @@ public Stream asStreamOfReaderHandle() { Stream.Builder builder = Stream.builder(); for (int i=0; i < partCount; i++) { BodyPart bodyPart = multipart.getBodyPart(i); - builder.accept(new ReaderHandle(NodeConverter.InputStreamToReader(bodyPart.getInputStream()))); + builder.accept(updateHandle(bodyPart, new ReaderHandle())); } return builder.build(); } catch (MessagingException e) { throw new MarkLogicIOException(e); - } catch (IOException e) { - throw new MarkLogicIOException(e); } } @Override @@ -6343,11 +6406,12 @@ public C[] asArrayOfContent( int partCount = multipart.getCount(); C[] result = outputHandle.newArray(hasEndpointState ? (partCount - 1) : partCount); for (int i=0; i < partCount; i++) { - C value = responsePartToContent(outputHandle, multipart.getBodyPart(i), receiveClass); + BodyPart bodyPart = multipart.getBodyPart(i); if (hasEndpointState && i == 0) { - endpointStateHandle.set(outputHandle.contentToBytes(value)); + updateHandle(bodyPart, endpointStateHandle); } else { - result[hasEndpointState ? (i - 1) : i] = value; + C value = responsePartToContent(outputHandle, bodyPart, receiveClass); + result[hasEndpointState ? (i - 1) : i] = value; } } @@ -6358,6 +6422,35 @@ public C[] asArrayOfContent( outputHandle.set(null); } } + @Override + public BufferableContentHandle[] asArrayOfHandles( + BytesHandle endpointStateHandle, BufferableContentHandle outputHandle + ) { + try { + if (multipart == null) { + return outputHandle.newHandleArray(0); + } + + boolean hasEndpointState = (endpointStateHandle != null); + + int partCount = multipart.getCount(); + BufferableContentHandle[] result = outputHandle.newHandleArray(hasEndpointState ? (partCount - 1) : partCount); + for (int i=0; i < partCount; i++) { + BodyPart bodyPart = multipart.getBodyPart(i); + if (hasEndpointState && i == 0) { + updateHandle(bodyPart, endpointStateHandle); + } else { + result[hasEndpointState ? (i - 1) : i] = updateHandle(bodyPart, outputHandle.newHandle()); + } + } + + return result; + } catch (MessagingException e) { + throw new MarkLogicIOException(e); + } finally { + outputHandle.set(null); + } + } private C responsePartToContent(BufferableContentHandle handle, BodyPart bodyPart, Class as) { return handle.toContent(getEntity(bodyPart, as)); } @@ -6392,13 +6485,11 @@ public InputStreamHandle[] asArrayOfInputStreamHandle() { InputStreamHandle[] result = new InputStreamHandle[partCount]; for (int i=0; i < partCount; i++) { BodyPart bodyPart = multipart.getBodyPart(i); - result[i] = new InputStreamHandle(bodyPart.getInputStream()); + result[i] = updateHandle(bodyPart, new InputStreamHandle()); } return result; } catch (MessagingException e) { throw new MarkLogicIOException(e); - } catch (IOException e) { - throw new MarkLogicIOException(e); } } @Override @@ -6412,13 +6503,11 @@ public ReaderHandle[] asArrayOfReaderHandle() { ReaderHandle[] result = new ReaderHandle[partCount]; for (int i=0; i < partCount; i++) { BodyPart bodyPart = multipart.getBodyPart(i); - result[i] = new ReaderHandle(NodeConverter.InputStreamToReader(bodyPart.getInputStream())); + result[i] = updateHandle(bodyPart, new ReaderHandle()); } return result; } catch (MessagingException e) { throw new MarkLogicIOException(e); - } catch (IOException e) { - throw new MarkLogicIOException(e); } } @Override @@ -6475,12 +6564,14 @@ static protected boolean checkNull(ResponseBody body, Format expectedFormat) { "Returned document with unknown mime type instead of "+expectedFormat.getDefaultMimetype() ); } - Format actualFormat = Format.getFromMimetype(actualType.toString()); - if (expectedFormat != actualFormat) { - body.close(); - throw new RuntimeException( - "Mime type "+actualType.toString()+" for returned document not recognized for "+expectedFormat.name() - ); + if (expectedFormat != Format.UNKNOWN) { + Format actualFormat = Format.getFromMimetype(actualType.toString()); + if (expectedFormat != actualFormat) { + body.close(); + throw new RuntimeException( + "Mime type "+actualType.toString()+" for returned document not recognized for "+expectedFormat.name() + ); + } } return false; } @@ -6498,11 +6589,13 @@ static protected boolean checkNull(MimeMultipart multipart, Format expectedForma "Returned document with unknown mime type instead of "+expectedFormat.getDefaultMimetype() ); } - Format actualFormat = Format.getFromMimetype(actualType); - if (expectedFormat != actualFormat) { - throw new RuntimeException( - "Mime type "+actualType+" for returned document not recognized for "+expectedFormat.name() - ); + if (expectedFormat != Format.UNKNOWN) { + Format actualFormat = Format.getFromMimetype(actualType); + if (expectedFormat != actualFormat) { + throw new RuntimeException( + "Mime type "+actualType+" for returned document not recognized for "+expectedFormat.name() + ); + } } return false; } @@ -6519,8 +6612,10 @@ static private void closeResponse(Response response) { } Request.Builder forDocumentResponse(Request.Builder requestBldr, Format format) { - return requestBldr.addHeader(HEADER_ACCEPT, (format == null || format == Format.BINARY) ? - "application/x-unknown-content-type" : format.getDefaultMimetype()); + return requestBldr.addHeader( + HEADER_ACCEPT, + (format == null || format == Format.BINARY || format == Format.UNKNOWN) ? + "application/x-unknown-content-type" : format.getDefaultMimetype()); } Request.Builder forMultipartMixedResponse(Request.Builder requestBldr) { diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/RESTServices.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/RESTServices.java index fc0c28848..1a3714d3e 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/RESTServices.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/RESTServices.java @@ -596,6 +596,7 @@ interface CallResponse { interface SingleCallResponse extends CallResponse { byte[] asBytes(); C asContent(BufferableContentHandle outputHandle); + > T asHandle(T outputHandle); InputStream asInputStream(); InputStreamHandle asInputStreamHandle(); Reader asReader(); @@ -607,6 +608,8 @@ interface SingleCallResponse extends CallResponse { interface MultipleCallResponse extends CallResponse { Stream asStreamOfBytes(); Stream asStreamOfContent(BytesHandle endpointStateHandle, BufferableContentHandle outputHandle); + > Stream + asStreamOfHandles(BytesHandle endpointStateHandle, T outputHandle); Stream asStreamOfInputStream(); Stream asStreamOfInputStreamHandle(); Stream asStreamOfReader(); @@ -614,6 +617,8 @@ interface MultipleCallResponse extends CallResponse { Stream asStreamOfString(); byte[][] asArrayOfBytes(); C[] asArrayOfContent(BytesHandle endpointStateHandle, BufferableContentHandle outputHandle); + BufferableContentHandle[] + asArrayOfHandles(BytesHandle endpointStateHandle, BufferableContentHandle outputHandle); InputStream[] asArrayOfInputStream(); InputStreamHandle[] asArrayOfInputStreamHandle(); Reader[] asArrayOfReader(); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/BytesHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/BytesHandle.java index 9e806c61c..99a46742b 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/BytesHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/BytesHandle.java @@ -151,6 +151,11 @@ public BytesHandle newHandle() { return new BytesHandle().withFormat(getFormat()).withMimetype(getMimetype()); } @Override + public BytesHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new BytesHandle[length]; + } + @Override public byte[][] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new byte[length][]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/DOMHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/DOMHandle.java index d90c1f26d..8a9ce56f7 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/DOMHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/DOMHandle.java @@ -157,6 +157,11 @@ public DOMHandle newHandle() { return new DOMHandle().withMimetype(getMimetype()); } @Override + public DOMHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new DOMHandle[length]; + } + @Override public Document[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new Document[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/FileHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/FileHandle.java index 55bef41d3..7da5b7278 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/FileHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/FileHandle.java @@ -124,6 +124,11 @@ public FileHandle newHandle() { return new FileHandle().withFormat(getFormat()).withMimetype(getMimetype()); } @Override + public FileHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new FileHandle[length]; + } + @Override public File[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new File[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java index 90daad226..8de0cb3f3 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/InputSourceHandle.java @@ -177,6 +177,11 @@ public InputSourceHandle newHandle() { return new InputSourceHandle().withMimetype(getMimetype()); } @Override + public InputSourceHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new InputSourceHandle[length]; + } + @Override public InputSource[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new InputSource[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/InputStreamHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/InputStreamHandle.java index ed1922abf..d47e4ca53 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/InputStreamHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/InputStreamHandle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,6 +146,11 @@ public InputStreamHandle newHandle() { return new InputStreamHandle().withFormat(getFormat()).withMimetype(getMimetype()); } @Override + public InputStreamHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new InputStreamHandle[length]; + } + @Override public InputStream[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new InputStream[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/JAXBHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/JAXBHandle.java index ece33dba8..d4b79dd13 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/JAXBHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/JAXBHandle.java @@ -180,6 +180,11 @@ public Class getContentClass() { public JAXBHandle newHandle() { return new JAXBHandle<>(context, getContentClass()).withMimetype(getMimetype()); } + @Override + public JAXBHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new JAXBHandle[length]; + } /** * Restricts the format to XML. diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonDatabindHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonDatabindHandle.java index 87ed51fcd..a14c43bf0 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonDatabindHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonDatabindHandle.java @@ -142,6 +142,11 @@ public Class getContentClass() { public JacksonDatabindHandle newHandle() { return new JacksonDatabindHandle<>(getContentClass()).withFormat(getFormat()).withMimetype(getMimetype()); } + @Override + public JacksonDatabindHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new JacksonDatabindHandle[length]; + } /** * Provides access to the ObjectMapper used internally so you can configure diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonHandle.java index 43aa8040a..4fe3a2b08 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonHandle.java @@ -136,6 +136,11 @@ public JacksonHandle newHandle() { return new JacksonHandle().withMimetype(getMimetype()); } @Override + public JacksonHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new JacksonHandle[length]; + } + @Override public JsonNode[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new JsonNode[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java index 68cf8b4dc..0f2f3b1b8 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java @@ -147,6 +147,11 @@ public JacksonParserHandle newHandle() { return new JacksonParserHandle().withMimetype(getMimetype()); } @Override + public JacksonParserHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new JacksonParserHandle[length]; + } + @Override public JsonParser[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new JsonParser[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/ReaderHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/ReaderHandle.java index f68ddddfa..1eed555fd 100755 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/ReaderHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/ReaderHandle.java @@ -134,6 +134,11 @@ public ReaderHandle newHandle() { return new ReaderHandle().withFormat(getFormat()).withMimetype(getMimetype()); } @Override + public ReaderHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new ReaderHandle[length]; + } + @Override public Reader[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new Reader[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java index 513214c03..878da7bd6 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/SourceHandle.java @@ -158,6 +158,11 @@ public SourceHandle newHandle() { return new SourceHandle().withMimetype(getMimetype()); } @Override + public SourceHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new SourceHandle[length]; + } + @Override public Source[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new Source[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/StringHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/StringHandle.java index be3654865..8cc193414 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/StringHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/StringHandle.java @@ -132,6 +132,11 @@ public StringHandle newHandle() { return new StringHandle().withFormat(getFormat()).withMimetype(getMimetype()); } @Override + public StringHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new StringHandle[length]; + } + @Override public String[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new String[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLEventReaderHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLEventReaderHandle.java index e09110f65..76c81cf35 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLEventReaderHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLEventReaderHandle.java @@ -158,6 +158,11 @@ public XMLEventReaderHandle newHandle() { return new XMLEventReaderHandle().withMimetype(getMimetype()); } @Override + public XMLEventReaderHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new XMLEventReaderHandle[length]; + } + @Override public XMLEventReader[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new XMLEventReader[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLStreamReaderHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLStreamReaderHandle.java index 72c9bdba3..9ea6892d0 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLStreamReaderHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/XMLStreamReaderHandle.java @@ -159,6 +159,11 @@ public XMLStreamReaderHandle newHandle() { return new XMLStreamReaderHandle().withMimetype(getMimetype()); } @Override + public XMLStreamReaderHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); + return new XMLStreamReaderHandle[length]; + } + @Override public XMLStreamReader[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return new XMLStreamReader[length]; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/BufferableContentHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/BufferableContentHandle.java index 4d67429a1..92845736d 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/BufferableContentHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/BufferableContentHandle.java @@ -15,6 +15,8 @@ */ package com.marklogic.client.io.marker; +import java.lang.reflect.Array; + /** * A Bufferable Content Handle provides an adapter for a content * representation that can be read multiple times for purposes @@ -43,24 +45,46 @@ public interface BufferableContentHandle extends BufferableHandle, Content */ byte[] contentToBytes(C content); + @Override + BufferableContentHandle newHandle(); + + @Override + default BufferableContentHandle newHandle(C content) { + BufferableContentHandle handle = newHandle(); + handle.set(content); + return handle; + } + + /** + * Constructs an uninitialized array with the specified length with items + * of the same content representation. + * @param length the number of positions in the array + * @return the uninitialized array + */ + @SuppressWarnings("unchecked") + default BufferableContentHandle[] newHandleArray(int length) { + if (length < 0) throw new IllegalArgumentException("handle array length less than zero: "+length); + return (BufferableContentHandle[]) Array.newInstance(this.getClass(), length); + } + /** * Provides a handle that can resend the content. * @param content the content * @return the resendable handle */ - BufferableHandle resendableHandleFor(C content); + BufferableContentHandle resendableHandleFor(C content); + /** * Provides an array of handles that can resend an array of content. * @param content the array of content * @return the array of resendable handles */ - default BufferableHandle[] resendableHandleFor(C[] content) { + default BufferableContentHandle[] resendableHandleFor(C[] content) { if (content == null) return null; - BufferableHandle[] result = new BufferableHandle[content.length]; + BufferableContentHandle[] result = new BufferableContentHandle[content.length]; for (int i=0; i < content.length; i++) { result[i] = resendableHandleFor(content[i]); } return result; } - } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ContentHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ContentHandle.java index 0a721a79c..f0163bed8 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ContentHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ContentHandle.java @@ -110,6 +110,7 @@ default ContentHandle newHandle(C content) { * @param length the size of the array (zero or more) * @return the constructed array */ + @SuppressWarnings("unchecked") default C[] newArray(int length) { if (length < 0) throw new IllegalArgumentException("array length less than zero: "+length); return (C[]) Array.newInstance(getContentClass(), length); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ResendableContentHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ResendableContentHandle.java index 1e77650cf..ec6fa56ad 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ResendableContentHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/ResendableContentHandle.java @@ -24,7 +24,19 @@ */ public interface ResendableContentHandle extends BufferableContentHandle { @Override - default BufferableHandle resendableHandleFor(C content) { - return (BufferableHandle) newHandle(content); + ResendableContentHandle newHandle(); + @Override + ResendableContentHandle[] newHandleArray(int length); + + @Override + default ResendableContentHandle newHandle(C content) { + ResendableContentHandle handle = newHandle(); + handle.set(content); + return handle; + } + + @Override + default ResendableContentHandle resendableHandleFor(C content) { + return newHandle(content); } } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/StreamingContentHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/StreamingContentHandle.java index 9e6870824..861ec277e 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/StreamingContentHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/marker/StreamingContentHandle.java @@ -19,16 +19,17 @@ import com.marklogic.client.io.BytesHandle; /** - * A Streaming Content Handle provides an adapter for a content - * representation to allow the content to be read multiple times - * for purposes such as resending input when retrying after a + * A Streaming Content Handle provides an adapter for a streaming content + * representation to make it possible to construct a bufferable content + * representation so the content can be read multiple times for purposes + * such as resending input when retrying after a * failed request. * @param the handled content representation * @param the serialization when reading the content */ public interface StreamingContentHandle extends BufferableContentHandle { @Override - default BufferableHandle resendableHandleFor(C content) { + default BufferableContentHandle resendableHandleFor(C content) { return new BytesHandle(contentToBytes(content)) .withFormat(((BaseHandle) this).getFormat()); } diff --git a/ml-development-tools/src/main/kotlin/com/marklogic/client/tools/proxy/Generator.kt b/ml-development-tools/src/main/kotlin/com/marklogic/client/tools/proxy/Generator.kt index 6420dc445..c71a4c122 100644 --- a/ml-development-tools/src/main/kotlin/com/marklogic/client/tools/proxy/Generator.kt +++ b/ml-development-tools/src/main/kotlin/com/marklogic/client/tools/proxy/Generator.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 MarkLogic Corporation + * Copyright (c) 2021 MarkLogic Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue +import com.marklogic.client.io.marker.BufferableContentHandle import java.io.File import com.networknt.schema.* @@ -115,6 +116,7 @@ class Generator { return mapOf( "array" to "Reader", "object" to "Reader", + "anyDocument" to "com.marklogic.client.io.InputStreamHandle", "binaryDocument" to "InputStream", "jsonDocument" to "Reader", "textDocument" to "Reader", @@ -172,6 +174,8 @@ class Generator { "java.lang.String", "com.fasterxml.jackson.databind.node.ArrayNode", "com.fasterxml.jackson.core.JsonParser"), + "anyDocument" to setOf( + "com.marklogic.client.io.InputStreamHandle"), "binaryDocument" to setOf( "java.io.InputStream"), "jsonDocument" to setOf( @@ -205,7 +209,7 @@ class Generator { return getAtomicDataTypes() + getDocumentDataTypes() } fun getAllMappings(): Map> { - return getAtomicMappings() + getDocumentMappings() + return getAtomicMappings() + getDocumentMappings().filterNot{entry -> (entry.key == "anyDocument")} } fun getJavaDataType( dataType: String, mapping: String?, dataKind: String, isMultiple: Boolean @@ -230,12 +234,22 @@ class Generator { val allMappings = getAllMappings() val typeMappings = allMappings[dataType] - if (typeMappings === null) { + if (typeMappings?.contains(mapping) == true) { + return mapping + } else if (dataType == "anyDocument") { + try { + val mappingClass = Class.forName(mapping) + if (!BufferableContentHandle::class.java.isAssignableFrom(mappingClass)) { + throw IllegalArgumentException("""mapped class ${mapping} for anyDocument data type must extend BufferableContentHandle""") + } + return mapping + } catch(e: ClassNotFoundException) { + throw IllegalArgumentException("""could not load mapped class ${mapping} for anyDocument data type""") + } + } else if (typeMappings === null) { throw IllegalArgumentException("""no mappings for data type ${dataType}""") - } else if (!typeMappings.contains(mapping)) { - throw IllegalArgumentException("""no mapping to ${mapping} for data type ${dataType}""") } - return mapping + throw IllegalArgumentException("""no mapping to ${mapping} for data type ${dataType}""") } fun getSigDataType(mappedType: String, isMultiple: Boolean): String { val sigType = @@ -642,26 +656,36 @@ ${funcDecls} """)} )""" - val returnConverter = - if (returnType === null || returnMapped === null) - "" - else + val returnFormat = + if (returnType === null || returnKind != "document") "null" + else typeFormat(returnType) + val returnChained = + if (returnKind === null) """.responseNone()""" + else if (returnMultiple == true) """.responseMultiple(${returnNullable}, ${returnFormat})""" + else """.responseSingle(${returnNullable}, ${returnFormat})""" + + val callImpl = """request${sessionFluent}${paramsFluent}${returnChained}""" + +// TODO: also array support? + val bodyImpl = + if (returnType === null || returnMapped === null) { + """${callImpl};""" + } else if (returnType == "anyDocument") { + """return ${callImpl} + ${ + if (returnMultiple) """.asStreamOfHandles(null, new ${returnMapped}())""" + else """.asHandle(new ${returnMapped}())""" + };""" + } else { """return BaseProxy.${typeConverter(returnType)}.to${ if (returnMapped.contains(".")) returnMapped.substringAfterLast(".").capitalize() else returnMapped.capitalize() }( - """ - val returnFormat = - if (returnType === null || returnKind != "document") "null" - else typeFormat(returnType) - val returnChained = - if (returnKind === null) """.responseNone()""" - else if (returnMultiple == true) """.responseMultiple(${returnNullable}, ${returnFormat}) - )""" - else """.responseSingle(${returnNullable}, ${returnFormat}) - )""" + ${callImpl} + );""" + } val fieldReturn = if (returnType === null) "" @@ -707,8 +731,7 @@ ${funcDecls} ); } private ${sigImpl} { - ${returnConverter - }request${sessionFluent}${paramsFluent}${returnChained}; + ${bodyImpl} }""" return defSource } @@ -794,13 +817,16 @@ ${funcDecls} return methods } fun paramConverter(paramName: String, paramKind: String, paramType: String, mappedType: String, isNullable: Boolean) : String { - val converter = - """BaseProxy.${paramKind}Param("${paramName}", ${isNullable}, BaseProxy.${typeConverter(paramType)}.from${ + val convertExpr = + if (paramType == "anyDocument") paramName + else """BaseProxy.${typeConverter(paramType)}.from${ if (mappedType.contains(".")) mappedType.substringAfterLast(".").capitalize() else mappedType.capitalize() - }(${paramName}))""" + }(${paramName})""" + val converter = + """BaseProxy.${paramKind}Param("${paramName}", ${isNullable}, ${convertExpr})""" return converter } fun typeConverter(datatype: String) : String { @@ -813,6 +839,7 @@ ${funcDecls} fun typeFormat(documentType: String) : String { val format = if (documentType == "array" || documentType == "object") "Format.JSON" + else if (documentType == "anyDocument") "Format.UNKNOWN" else "Format."+documentType.substringBefore("Document").toUpperCase() return format } diff --git a/ml-development-tools/src/main/resources/proxy/endpointDeclarationSchema.json b/ml-development-tools/src/main/resources/proxy/endpointDeclarationSchema.json index 8dfe01c0a..46e6929a6 100644 --- a/ml-development-tools/src/main/resources/proxy/endpointDeclarationSchema.json +++ b/ml-development-tools/src/main/resources/proxy/endpointDeclarationSchema.json @@ -12,7 +12,7 @@ "boolean", "date", "dateTime", "dayTimeDuration", "decimal", "double", "float", "int", "long", "string", "time", "unsignedInt", "unsignedLong", "array", "object", - "binaryDocument", "jsonDocument", "textDocument", "xmlDocument", + "anyDocument", "binaryDocument", "jsonDocument", "textDocument", "xmlDocument", "session" ] }, diff --git a/ml-development-tools/src/test/java/com/marklogic/client/test/dbfunction/positive/AnyDocumentBundle.java b/ml-development-tools/src/test/java/com/marklogic/client/test/dbfunction/positive/AnyDocumentBundle.java new file mode 100644 index 000000000..abff1ed0f --- /dev/null +++ b/ml-development-tools/src/test/java/com/marklogic/client/test/dbfunction/positive/AnyDocumentBundle.java @@ -0,0 +1,197 @@ +package com.marklogic.client.test.dbfunction.positive; + +// IMPORTANT: Do not edit. This file is generated. + +import java.util.stream.Stream; +import com.marklogic.client.io.Format; + + +import com.marklogic.client.DatabaseClient; +import com.marklogic.client.io.marker.JSONWriteHandle; + +import com.marklogic.client.impl.BaseProxy; + +/** + * Provides a set of operations on the database server + */ +public interface AnyDocumentBundle { + /** + * Creates a AnyDocumentBundle object for executing operations on the database server. + * + * The DatabaseClientFactory class can create the DatabaseClient parameter. A single + * client object can be used for any number of requests and in multiple threads. + * + * @param db provides a client for communicating with the database server + * @return an object for executing database operations + */ + static AnyDocumentBundle on(DatabaseClient db) { + return on(db, null); + } + /** + * Creates a AnyDocumentBundle object for executing operations on the database server. + * + * The DatabaseClientFactory class can create the DatabaseClient parameter. A single + * client object can be used for any number of requests and in multiple threads. + * + * The service declaration uses a custom implementation of the same service instead + * of the default implementation of the service by specifying an endpoint directory + * in the modules database with the implementation. A service.json file with the + * declaration can be read with FileHandle or a string serialization of the JSON + * declaration with StringHandle. + * + * @param db provides a client for communicating with the database server + * @param serviceDeclaration substitutes a custom implementation of the service + * @return an object for executing database operations + */ + static AnyDocumentBundle on(DatabaseClient db, JSONWriteHandle serviceDeclaration) { + final class AnyDocumentBundleImpl implements AnyDocumentBundle { + private DatabaseClient dbClient; + private BaseProxy baseProxy; + + private BaseProxy.DBFunctionRequest req_sendReceiveManyDocs; + private BaseProxy.DBFunctionRequest req_sendReceiveRequiredDoc; + private BaseProxy.DBFunctionRequest req_sendReceiveOptionalDoc; + private BaseProxy.DBFunctionRequest req_sendReceiveAnyDocs; + private BaseProxy.DBFunctionRequest req_sendReceiveMappedDoc; + + private AnyDocumentBundleImpl(DatabaseClient dbClient, JSONWriteHandle servDecl) { + this.dbClient = dbClient; + this.baseProxy = new BaseProxy("/dbf/test/anyDocument/", servDecl); + + this.req_sendReceiveManyDocs = this.baseProxy.request( + "sendReceiveManyDocs.sjs", BaseProxy.ParameterValuesKind.MULTIPLE_MIXED); + this.req_sendReceiveRequiredDoc = this.baseProxy.request( + "sendReceiveRequiredDoc.sjs", BaseProxy.ParameterValuesKind.MULTIPLE_MIXED); + this.req_sendReceiveOptionalDoc = this.baseProxy.request( + "sendReceiveOptionalDoc.sjs", BaseProxy.ParameterValuesKind.MULTIPLE_MIXED); + this.req_sendReceiveAnyDocs = this.baseProxy.request( + "sendReceiveAnyDocs.sjs", BaseProxy.ParameterValuesKind.MULTIPLE_MIXED); + this.req_sendReceiveMappedDoc = this.baseProxy.request( + "sendReceiveMappedDoc.sjs", BaseProxy.ParameterValuesKind.MULTIPLE_MIXED); + } + + @Override + public Stream sendReceiveManyDocs(Stream uris, Stream docs) { + return sendReceiveManyDocs( + this.req_sendReceiveManyDocs.on(this.dbClient), uris, docs + ); + } + private Stream sendReceiveManyDocs(BaseProxy.DBFunctionRequest request, Stream uris, Stream docs) { + return request + .withParams( + BaseProxy.atomicParam("uris", false, BaseProxy.StringType.fromString(uris)), + BaseProxy.documentParam("docs", false, docs) + ).responseMultiple(false, Format.UNKNOWN) + .asStreamOfHandles(null, new com.marklogic.client.io.InputStreamHandle()); + } + + @Override + public com.marklogic.client.io.InputStreamHandle sendReceiveRequiredDoc(String uri, com.marklogic.client.io.InputStreamHandle doc) { + return sendReceiveRequiredDoc( + this.req_sendReceiveRequiredDoc.on(this.dbClient), uri, doc + ); + } + private com.marklogic.client.io.InputStreamHandle sendReceiveRequiredDoc(BaseProxy.DBFunctionRequest request, String uri, com.marklogic.client.io.InputStreamHandle doc) { + return request + .withParams( + BaseProxy.atomicParam("uri", false, BaseProxy.StringType.fromString(uri)), + BaseProxy.documentParam("doc", false, doc) + ).responseSingle(false, Format.UNKNOWN) + .asHandle(new com.marklogic.client.io.InputStreamHandle()); + } + + @Override + public com.marklogic.client.io.InputStreamHandle sendReceiveOptionalDoc(String uri, com.marklogic.client.io.InputStreamHandle doc) { + return sendReceiveOptionalDoc( + this.req_sendReceiveOptionalDoc.on(this.dbClient), uri, doc + ); + } + private com.marklogic.client.io.InputStreamHandle sendReceiveOptionalDoc(BaseProxy.DBFunctionRequest request, String uri, com.marklogic.client.io.InputStreamHandle doc) { + return request + .withParams( + BaseProxy.atomicParam("uri", true, BaseProxy.StringType.fromString(uri)), + BaseProxy.documentParam("doc", true, doc) + ).responseSingle(true, Format.UNKNOWN) + .asHandle(new com.marklogic.client.io.InputStreamHandle()); + } + + @Override + public Stream sendReceiveAnyDocs(Stream uris, Stream docs) { + return sendReceiveAnyDocs( + this.req_sendReceiveAnyDocs.on(this.dbClient), uris, docs + ); + } + private Stream sendReceiveAnyDocs(BaseProxy.DBFunctionRequest request, Stream uris, Stream docs) { + return request + .withParams( + BaseProxy.atomicParam("uris", true, BaseProxy.StringType.fromString(uris)), + BaseProxy.documentParam("docs", true, docs) + ).responseMultiple(true, Format.UNKNOWN) + .asStreamOfHandles(null, new com.marklogic.client.io.InputStreamHandle()); + } + + @Override + public com.marklogic.client.io.StringHandle sendReceiveMappedDoc(String uri, com.marklogic.client.io.StringHandle doc) { + return sendReceiveMappedDoc( + this.req_sendReceiveMappedDoc.on(this.dbClient), uri, doc + ); + } + private com.marklogic.client.io.StringHandle sendReceiveMappedDoc(BaseProxy.DBFunctionRequest request, String uri, com.marklogic.client.io.StringHandle doc) { + return request + .withParams( + BaseProxy.atomicParam("uri", true, BaseProxy.StringType.fromString(uri)), + BaseProxy.documentParam("doc", true, doc) + ).responseSingle(true, Format.UNKNOWN) + .asHandle(new com.marklogic.client.io.StringHandle()); + } + } + + return new AnyDocumentBundleImpl(db, serviceDeclaration); + } + + /** + * Invokes the sendReceiveManyDocs operation on the database server + * + * @param uris provides input + * @param docs provides input + * @return as output + */ + Stream sendReceiveManyDocs(Stream uris, Stream docs); + + /** + * Invokes the sendReceiveRequiredDoc operation on the database server + * + * @param uri provides input + * @param doc provides input + * @return as output + */ + com.marklogic.client.io.InputStreamHandle sendReceiveRequiredDoc(String uri, com.marklogic.client.io.InputStreamHandle doc); + + /** + * Invokes the sendReceiveOptionalDoc operation on the database server + * + * @param uri provides input + * @param doc provides input + * @return as output + */ + com.marklogic.client.io.InputStreamHandle sendReceiveOptionalDoc(String uri, com.marklogic.client.io.InputStreamHandle doc); + + /** + * Invokes the sendReceiveAnyDocs operation on the database server + * + * @param uris provides input + * @param docs provides input + * @return as output + */ + Stream sendReceiveAnyDocs(Stream uris, Stream docs); + + /** + * Invokes the sendReceiveMappedDoc operation on the database server + * + * @param uri provides input + * @param doc provides input + * @return as output + */ + com.marklogic.client.io.StringHandle sendReceiveMappedDoc(String uri, com.marklogic.client.io.StringHandle doc); + +} diff --git a/ml-development-tools/src/test/java/com/marklogic/client/test/dbfunction/positive/AnyDocumentBundleTest.java b/ml-development-tools/src/test/java/com/marklogic/client/test/dbfunction/positive/AnyDocumentBundleTest.java new file mode 100644 index 000000000..011ab9662 --- /dev/null +++ b/ml-development-tools/src/test/java/com/marklogic/client/test/dbfunction/positive/AnyDocumentBundleTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2019 MarkLogic Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.marklogic.client.test.dbfunction.positive; + +import com.marklogic.client.FailedRequestException; +import com.marklogic.client.impl.BaseProxy; +import com.marklogic.client.io.BaseHandle; +import com.marklogic.client.io.Format; +import com.marklogic.client.io.InputStreamHandle; +import com.marklogic.client.io.StringHandle; +import com.marklogic.client.io.marker.BufferableContentHandle; +import com.marklogic.client.test.dbfunction.DBFunctionTestUtil; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import static org.junit.Assert.*; + +public class AnyDocumentBundleTest { + final static private String[] uris = {"/test/anyDocument1.json", "/test/anyDocument1.xml"}; + final static private Format[] formats = {Format.JSON, Format.XML}; + // docs must align with the formats array + final static private String[] docs = { + "{\"key1\":\"value1\"}", + "\ntext1" + }; + final static private AnyDocumentBundle testObj = AnyDocumentBundle.on(DBFunctionTestUtil.db); + + @Test + public void sendReceiveOptionalJSONDocTest() { + testInputStreamImpl(uris[0], Format.JSON, docs[0], testObj::sendReceiveOptionalDoc); + } + @Test + public void sendReceiveOptionalXMLDocTest() { + testInputStreamImpl(uris[1], Format.XML, docs[1], testObj::sendReceiveOptionalDoc); + } + @Test + public void sendReceiveOptionalNullUriTest() { + // send null uri with non-null handle to optional endpoint + testInputStreamImpl(null, Format.JSON, docs[0], testObj::sendReceiveOptionalDoc); + } + @Test + public void sendReceiveOptionalNullDocTest() { + // send non-null uri with null handle to optional endpoint + testInputStreamImpl(uris[0], null, null, testObj::sendReceiveOptionalDoc); + } + @Test + public void sendReceiveOptionalNullUriDocTest() { + // send null uri and null handle to optional endpoint + testInputStreamImpl(null, null, null, testObj::sendReceiveOptionalDoc); + } + + @Test + public void sendReceiveRequiredJSONDocTest() { + testInputStreamImpl(uris[0], Format.JSON, docs[0], testObj::sendReceiveRequiredDoc); + } + @Test + public void sendReceiveRequiredXMLDocTest() { + testInputStreamImpl(uris[1], Format.XML, docs[1], testObj::sendReceiveRequiredDoc); + } + @Test + public void sendReceiveRequiredNullDocTest() { + // negative test: send non-null uri with null handle to required endpoint + try { + testInputStreamImpl(uris[0], null, null, testObj::sendReceiveRequiredDoc); + fail("no exception for required parameter with null value"); + } catch(BaseProxy.RequiredParamException e) { + assertEquals("null value for required parameter: doc", e.getMessage()); + } + } + @Test + public void sendReceiveOptionalInvalidFormatDocTest() { + // negative test: send handle with invalid format + try { + testInputStreamImpl(uris[0], formats[1], docs[0], testObj::sendReceiveOptionalDoc); + fail("no exception for invalid format"); + } catch(FailedRequestException e) { + assertEquals(400, e.getServerStatusCode()); + assertEquals("XDMP-DOCROOTTEXT", e.getServerMessageCode()); + } + } + + @Test + public void sendReceiveMappedJSONDocTest() { + testStringImpl(uris[0], Format.JSON, docs[0], testObj::sendReceiveMappedDoc); + } + @Test + public void sendReceiveMappedXMLDocTest() { + testStringImpl(uris[1], Format.XML, docs[1], testObj::sendReceiveMappedDoc); + } + @Test + public void sendReceiveMappedNullUriTest() { + // send null uri with non-null handle to optional endpoint + testStringImpl(null, Format.JSON, docs[0], testObj::sendReceiveMappedDoc); + } + @Test + public void sendReceiveMappedNullDocTest() { + // send non-null uri with null handle to optional endpoint + testStringImpl(uris[0], null, null, testObj::sendReceiveMappedDoc); + } + @Test + public void sendReceiveMappedNullUriDocTest() { + // send null uri and null handle to optional endpoint + testStringImpl(null, null, null, testObj::sendReceiveMappedDoc); + } + + @Test + public void sendReceiveAnyTwoDocsTest() { + testDocsImpl(uris, formats, docs, testObj::sendReceiveAnyDocs); + } + @Test + public void sendReceiveAnyOneJSONDocTest() { + testDocsImpl(uris[0], formats[0], docs[0], testObj::sendReceiveAnyDocs); + } + @Test + public void sendReceiveAnyOneXMLDocTest() { + testDocsImpl(uris[1], formats[1], docs[1], testObj::sendReceiveAnyDocs); + } + @Test + public void sendReceiveAnyNullUrisTest() { + testDocsImpl(null, formats, docs, testObj::sendReceiveAnyDocs); + } + @Test + public void sendReceiveAnyNullDocsTest() { + testDocsImpl(uris, null, null, testObj::sendReceiveAnyDocs); + } + @Test + public void sendReceiveAnyNullUrisDocsTest() { + testDocsImpl(null, (Format[]) null, null, testObj::sendReceiveAnyDocs); + } + + @Test + public void sendReceiveManyTwoDocsTest() { + testDocsImpl(uris, formats, docs, testObj::sendReceiveManyDocs); + } + @Test + public void sendReceiveManyOneJSONDocTest() { + testDocsImpl(uris[0], formats[0], docs[0], testObj::sendReceiveManyDocs); + } + @Test + public void sendReceiveManyOneXMLDocTest() { + testDocsImpl(uris[1], formats[1], docs[1], testObj::sendReceiveManyDocs); + } + @Test + public void sendReceiveManyNullDocsTest() { + try { + testDocsImpl(uris, null, null, testObj::sendReceiveManyDocs); + fail("no exception for required parameter with null value"); + } catch(FailedRequestException e) { + assertEquals(400, e.getServerStatusCode()); + assertEquals("XDMP-ENDPOINTNULLABLE", e.getServerMessageCode()); + assertTrue(e.getServerMessage().contains(" docs ") && e.getServerMessage().contains(" parameter ")); + } + } + @Test + public void sendReceiveAnyInvalidFormatDocsTest() { + // negative test: send handle with invalid format + try { + testDocsImpl(uris[1], formats[0], docs[1], testObj::sendReceiveAnyDocs); + fail("no exception for invalid format"); + } catch(FailedRequestException e) { + assertEquals(400, e.getServerStatusCode()); + assertEquals("XDMP-JSONDOC", e.getServerMessageCode()); + } + } + + private void testStringImpl(String uri, Format format, String doc, BiFunction caller) { + testDocImpl(uri, format, (doc == null) ? null : new StringHandle(doc), caller); + } + private void testInputStreamImpl(String uri, Format format, String doc, BiFunction caller) { + testDocImpl(uri, format, (doc == null) ? null : new InputStreamHandle(new ByteArrayInputStream(doc.getBytes())), caller); + } + private > void testDocImpl(String uri, Format format, T inputHandle, BiFunction caller) { + if (inputHandle != null && format != null) { + BaseHandle inputBase = (BaseHandle) inputHandle; + inputBase.setFormat(format); + } + testDocImpl(uri, inputHandle, caller); + } + private > void testDocImpl(String uri, T inputHandle, BiFunction caller) { + T outputHandle = caller.apply(uri, inputHandle); + + if (inputHandle != null) { + assertNotNull(outputHandle); + BaseHandle inputBase = (BaseHandle) inputHandle; + BaseHandle outputBase = (BaseHandle) outputHandle; + assertEquals(inputBase.getFormat(), outputBase.getFormat()); + assertEquals(new String(inputHandle.toBuffer()).trim(), new String(outputHandle.toBuffer()).trim()); + } else { + assertNull(outputHandle); + } + } + + private void testDocsImpl( + String uri, Format format, String doc, BiFunction,Stream,Stream> caller + ) { + testDocsImpl(new String[]{uri}, new Format[]{format}, new String[]{doc}, caller); + } + private void testDocsImpl( + String[] uris, Format[] formats, String[] docs, BiFunction,Stream,Stream> caller + ) { + Stream uriStream = (uris == null || uris.length == 0) ? Stream.empty() : Stream.of(uris); + + Stream inputHandleStream; + if (docs == null || docs.length == 0) { + inputHandleStream = Stream.empty(); + } else { + InputStreamHandle[] inputHandles = new InputStreamHandle[docs.length]; + for (int i=0; i < docs.length; i++) { + InputStreamHandle inputHandle = new InputStreamHandle(); + if (formats != null && i < formats.length) { + inputHandle.setFormat(formats[i]); + } + inputHandle.fromBuffer(docs[i].getBytes()); + inputHandles[i] = inputHandle; + } + inputHandleStream = Stream.of(inputHandles); + } + + Stream outputStream = caller.apply(uriStream, inputHandleStream); + + InputStreamHandle[] outputHandles = outputStream.toArray(InputStreamHandle[]::new); + if (docs == null || docs.length == 0) { + assertEquals(0, outputHandles.length); + } else { + assertEquals(docs.length, outputHandles.length); + for (int i=0; i < docs.length; i++) { + assertEquals(formats[i], outputHandles[i].getFormat()); + assertEquals(docs[i], new String(outputHandles[i].toBuffer()).trim()); + } + } + } +} diff --git a/ml-development-tools/src/test/kotlin/com/marklogic/client/test/dbfunction/fntestgen.kt b/ml-development-tools/src/test/kotlin/com/marklogic/client/test/dbfunction/fntestgen.kt index 931650db4..7176b9a1f 100644 --- a/ml-development-tools/src/test/kotlin/com/marklogic/client/test/dbfunction/fntestgen.kt +++ b/ml-development-tools/src/test/kotlin/com/marklogic/client/test/dbfunction/fntestgen.kt @@ -36,7 +36,7 @@ const val TEST_PACKAGE = "com.marklogic.client.test.dbfunction.generated" val generator = Generator() val atomicMap = generator.getAtomicDataTypes() -val documentMap = generator.getDocumentDataTypes() +val documentMap = generator.getDocumentDataTypes().filterNot{entry -> (entry.key == "anyDocument")} val mapper = jacksonObjectMapper() val serializer = mapper.writerWithDefaultPrettyPrinter() @@ -1604,7 +1604,7 @@ if (true) { val testList = when (testType) { "negative" -> listOf("badExecution") - "positive" -> listOf("decoratorBase", "decoratorCustom", "described", "mimetype", "sessions") + "positive" -> listOf("anyDocument", "decoratorBase", "decoratorCustom", "described", "mimetype", "sessions") else -> throw IllegalStateException("Unknown test type of $testType") } for (testName in testList) { diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveAnyDocs.api b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveAnyDocs.api new file mode 100644 index 000000000..b6d4d871d --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveAnyDocs.api @@ -0,0 +1,19 @@ +{ + "functionName": "sendReceiveAnyDocs", + "params": [ { + "name": "uris", + "datatype": "string", + "multiple": true, + "nullable": true + }, { + "name": "docs", + "datatype": "anyDocument", + "multiple": true, + "nullable": true + } ], + "return": { + "datatype": "anyDocument", + "multiple": true, + "nullable": true + } +} \ No newline at end of file diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveAnyDocs.sjs b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveAnyDocs.sjs new file mode 100644 index 000000000..2ccb75162 --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveAnyDocs.sjs @@ -0,0 +1,8 @@ +'use strict'; +var uris; +var docs; + +// could write to the database at this point... + +// instead, just return the documents in the same order for inspection on the client +docs; diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveManyDocs.api b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveManyDocs.api new file mode 100644 index 000000000..b05b50d2d --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveManyDocs.api @@ -0,0 +1,19 @@ +{ + "functionName": "sendReceiveManyDocs", + "params": [ { + "name": "uris", + "datatype": "string", + "multiple": true, + "nullable": false + }, { + "name": "docs", + "datatype": "anyDocument", + "multiple": true, + "nullable": false + } ], + "return": { + "datatype": "anyDocument", + "multiple": true, + "nullable": false + } +} \ No newline at end of file diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveManyDocs.sjs b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveManyDocs.sjs new file mode 100644 index 000000000..2ccb75162 --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveManyDocs.sjs @@ -0,0 +1,8 @@ +'use strict'; +var uris; +var docs; + +// could write to the database at this point... + +// instead, just return the documents in the same order for inspection on the client +docs; diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveMappedDoc.api b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveMappedDoc.api new file mode 100644 index 000000000..ca3ff97aa --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveMappedDoc.api @@ -0,0 +1,21 @@ +{ + "functionName": "sendReceiveMappedDoc", + "params": [ { + "name": "uri", + "datatype": "string", + "multiple": false, + "nullable": true + }, { + "name": "doc", + "datatype": "anyDocument", + "multiple": false, + "nullable": true, + "$javaClass" : "com.marklogic.client.io.StringHandle" + } ], + "return": { + "datatype": "anyDocument", + "multiple": false, + "nullable": true, + "$javaClass" : "com.marklogic.client.io.StringHandle" + } +} \ No newline at end of file diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveMappedDoc.sjs b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveMappedDoc.sjs new file mode 100644 index 000000000..ac1c5dd34 --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveMappedDoc.sjs @@ -0,0 +1,8 @@ +'use strict'; +var uri; +var doc; + +// could write to the database at this point... + +// instead, just return the documents in the same order for inspection on the client +doc; diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveOptionalDoc.api b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveOptionalDoc.api new file mode 100644 index 000000000..246549d68 --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveOptionalDoc.api @@ -0,0 +1,19 @@ +{ + "functionName": "sendReceiveOptionalDoc", + "params": [ { + "name": "uri", + "datatype": "string", + "multiple": false, + "nullable": true + }, { + "name": "doc", + "datatype": "anyDocument", + "multiple": false, + "nullable": true + } ], + "return": { + "datatype": "anyDocument", + "multiple": false, + "nullable": true + } +} \ No newline at end of file diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveOptionalDoc.sjs b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveOptionalDoc.sjs new file mode 100644 index 000000000..ac1c5dd34 --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveOptionalDoc.sjs @@ -0,0 +1,8 @@ +'use strict'; +var uri; +var doc; + +// could write to the database at this point... + +// instead, just return the documents in the same order for inspection on the client +doc; diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveRequiredDoc.api b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveRequiredDoc.api new file mode 100644 index 000000000..2045ac7db --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveRequiredDoc.api @@ -0,0 +1,19 @@ +{ + "functionName": "sendReceiveRequiredDoc", + "params": [ { + "name": "uri", + "datatype": "string", + "multiple": false, + "nullable": false + }, { + "name": "doc", + "datatype": "anyDocument", + "multiple": false, + "nullable": false + } ], + "return": { + "datatype": "anyDocument", + "multiple": false, + "nullable": false + } +} \ No newline at end of file diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveRequiredDoc.sjs b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveRequiredDoc.sjs new file mode 100644 index 000000000..ac1c5dd34 --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/sendReceiveRequiredDoc.sjs @@ -0,0 +1,8 @@ +'use strict'; +var uri; +var doc; + +// could write to the database at this point... + +// instead, just return the documents in the same order for inspection on the client +doc; diff --git a/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/service.json b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/service.json new file mode 100644 index 000000000..fdceb4594 --- /dev/null +++ b/ml-development-tools/src/test/ml-modules/root/dbfunctiondef/positive/anyDocument/service.json @@ -0,0 +1,4 @@ +{ + "endpointDirectory" : "/dbf/test/anyDocument/", + "$javaClass" : "com.marklogic.client.test.dbfunction.positive.AnyDocumentBundle" +} \ No newline at end of file