All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.marklogic.client.dataservices.impl.InputEndpointImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2024 MarkLogic Corporation. All Rights Reserved.
 */
package com.marklogic.client.dataservices.impl;

import java.io.InputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;

import com.marklogic.client.dataservices.InputCaller;
import com.marklogic.client.io.marker.BufferableContentHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.marklogic.client.DatabaseClient;
import com.marklogic.client.SessionState;
import com.marklogic.client.io.marker.JSONWriteHandle;

public class InputEndpointImpl extends IOEndpointImpl implements InputCaller {
	private static final Logger logger = LoggerFactory.getLogger(InputEndpointImpl.class);

	private final InputCallerImpl caller;
	private final int batchSize;

	public InputEndpointImpl(
        DatabaseClient client, JSONWriteHandle apiDecl, HandleProvider handleProvider
	) {
		this(client, new InputCallerImpl<>(apiDecl, handleProvider));
	}
	private InputEndpointImpl(DatabaseClient client, InputCallerImpl caller) {
		super(client, caller);
		this.caller = caller;
		this.batchSize = initBatchSize(caller);
	}

	private InputCallerImpl getCaller() {
		return this.caller;
	}
	private int getBatchSize() {
		return this.batchSize;
	}

	@Override
	public void call(I[] input) {
		call(newCallContext(), input);
	}
	@Override
	public void call(CallContext callContext, I[] input) {
		InputCallerImpl callerImpl = getCaller();
		BufferableContentHandle[] inputHandles = callerImpl.bufferableInputHandleOn(input);
		callerImpl.arrayCall(getClient(), checkAllowedArgs(callContext), inputHandles);
	}

	@Override
	public BulkInputCaller bulkCaller() {
		return new BulkInputCallerImpl<>(this);
	}
	@Override
	public BulkInputCaller bulkCaller(CallContext callContext) {
		return new BulkInputCallerImpl<>(this, getBatchSize(), checkAllowedArgs(callContext));
	}
	@Override
	public BulkInputCaller bulkCaller(CallContext[] callContexts) {
		if(callContexts == null || callContexts.length==0)
			throw new IllegalArgumentException("CallContext cannot be null or empty");
		return bulkCaller(callContexts, callContexts.length);
	}
	@Override
	public BulkInputCaller bulkCaller(CallContext[] callContexts, int threadCount) {
		if(callContexts == null)
			throw new IllegalArgumentException("CallContext cannot be null.");
		if(threadCount > callContexts.length)
			throw new IllegalArgumentException("Thread count cannot be more than the callContext count.");

		switch(callContexts.length) {
			case 0: throw new IllegalArgumentException("CallContext cannot be empty");
			case 1: return new BulkInputCallerImpl<>(this, getBatchSize(), checkAllowedArgs(callContexts[0]));
			default: return new BulkInputCallerImpl<>(this, getBatchSize(), checkAllowedArgs(callContexts), threadCount);
		}
	}

	public static class BulkInputCallerImpl extends IOEndpointImpl.BulkIOEndpointCallerImpl
			implements InputCaller.BulkInputCaller {

		private final InputEndpointImpl endpoint;
		private final int batchSize;
		private final LinkedBlockingQueue inputQueue;
		private ErrorListener errorListener;

		public BulkInputCallerImpl(InputEndpointImpl endpoint) {
			this(endpoint, endpoint.getBatchSize(), endpoint.checkAllowedArgs(endpoint.newCallContext()));
		}
		private BulkInputCallerImpl(InputEndpointImpl endpoint, int batchSize, CallContextImpl callContext) {
			super(endpoint, callContext);
			checkEndpoint(endpoint, "InputEndpointImpl");
			this.endpoint = endpoint;
			this.batchSize = batchSize;
			this.inputQueue = new LinkedBlockingQueue<>();
		}
		private BulkInputCallerImpl(
				InputEndpointImpl endpoint, int batchSize, CallContextImpl[] callContexts, int threadCount
		) {
			super(endpoint, callContexts, threadCount, (2*callContexts.length));
			this.endpoint = endpoint;
			this.batchSize = batchSize;
			this.inputQueue = new LinkedBlockingQueue<>();
		}

		private InputEndpointImpl getEndpoint() {
			return endpoint;
		}
		private int getBatchSize() {
			return batchSize;
		}
		private LinkedBlockingQueue getInputQueue() {
			return inputQueue;
		}

		@Override
		public void accept(I input) {
			boolean hasBatch = queueInput(input, getInputQueue(), getBatchSize());
			if (hasBatch)
			    processInput();
		}
		@Override
		public void acceptAll(I[] input) {
			boolean hasBatch = queueAllInput(input, getInputQueue(), getBatchSize());
			if (hasBatch)
				processInput();
		}

		private ErrorListener getErrorListener() {
			return this.errorListener;
		}

		@Override
		public void setErrorListener(ErrorListener errorListener) {
			this.errorListener = errorListener;
		}

		@Override
		public void awaitCompletion() {
			try {
				if (getInputQueue() != null) {
					while (!getInputQueue().isEmpty()) {
						processInput();
					}
				}

				// calling in concurrent threads
				if(getCallerThreadPoolExecutor() != null) {
					getCallerThreadPoolExecutor().shutdown();
					getCallerThreadPoolExecutor().awaitTermination();
				}
			} catch (Throwable throwable) {
				throw new RuntimeException("Error occurred while awaiting termination "+throwable.getMessage());
			}
		}
		private void processInput() {
			I[] inputBatch = getInputBatch(getInputQueue(), getBatchSize());
			if(getCallContext()!=null)
				processInput(getCallContext(), inputBatch);
			// TODO : optimize the case of a single thread with a callContextQueue.
			else if(getCallContextQueue() != null){
					BulkCallableImpl bulkCallableImpl = new BulkCallableImpl<>(this, inputBatch);
					submitTask(bulkCallableImpl);
			} else {
				throw new IllegalArgumentException("Cannot process input without Callcontext.");
			}
		}

		private void processInput(CallContextImpl callContext, I[] inputBatch) {
			logger.trace("input endpoint running endpoint={} count={} state={}", (callContext).getEndpoint().getEndpointPath(), getCallCount(),
					callContext.getEndpointState());
			InputCallerImpl callerImpl = getEndpoint().getCaller();

			ErrorDisposition error = ErrorDisposition.RETRY;

			BufferableContentHandle[] inputHandles = callerImpl.bufferableInputHandleOn(inputBatch);
			for (int retryCount = 0; retryCount < DEFAULT_MAX_RETRIES && error == ErrorDisposition.RETRY; retryCount++) {
				Throwable throwable = null;
				try {
					getEndpoint().getCaller().arrayCall(callContext.getClient(), callContext, inputHandles);
					incrementCallCount();
					return;
				} catch (Throwable catchedThrowable) {
					throwable = catchedThrowable;
				}

				if (throwable != null) {
					if (getErrorListener() == null) {
						logger.error("No error listener set. Stop all calls. " + getEndpoint().getEndpointPath(), throwable);
						error = ErrorDisposition.STOP_ALL_CALLS;
					} else {
						try {
							if (retryCount < DEFAULT_MAX_RETRIES - 1) {
								error = getErrorListener().processError(
										retryCount, throwable, callContext, inputHandles
								);
							} else {
								error = ErrorDisposition.SKIP_CALL;
							}

						} catch (Throwable throwable1) {
							logger.error("Error Listener failed with " , throwable1);
							error = ErrorDisposition.STOP_ALL_CALLS;
						}

						switch (error) {
							case RETRY:
								continue;

							case SKIP_CALL:
								return;

							case STOP_ALL_CALLS:
								if (getCallerThreadPoolExecutor() != null) {
									getCallerThreadPoolExecutor().shutdown();
								}
						}
					}
				}
			}
		}

		private static class BulkCallableImpl implements Callable {
			private final BulkInputCallerImpl bulkInputCallerImpl;
			private final I[] inputBatch;
			BulkCallableImpl(BulkInputCallerImpl bulkInputCallerImpl, I[] inputBatch) {
				this.bulkInputCallerImpl = bulkInputCallerImpl;
				this.inputBatch = inputBatch;
			}
			@Override
			public Boolean call() {
				try {
					CallContextImpl callContext = bulkInputCallerImpl.getCallContextQueue().take();

					bulkInputCallerImpl.processInput(callContext, inputBatch);
					bulkInputCallerImpl.getCallContextQueue().put(callContext);

				} catch(Throwable throwable) {
					throw new InternalError("Error occurred while processing CallContext - "+throwable.getMessage());
				}
				return true;
			}
		}
	}
}