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

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

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

import com.marklogic.client.DatabaseClient;
import com.marklogic.client.MarkLogicInternalException;
import com.marklogic.client.SessionState;
import com.marklogic.client.dataservices.ExecCaller;
import com.marklogic.client.io.marker.JSONWriteHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;

public class ExecEndpointImpl extends IOEndpointImpl implements ExecCaller {
    private static final Logger logger = LoggerFactory.getLogger(ExecEndpointImpl.class);
    private final ExecCallerImpl caller;

    public ExecEndpointImpl(DatabaseClient client, JSONWriteHandle apiDecl) {
        this(client, new ExecCallerImpl<>(apiDecl));
    }
    private ExecEndpointImpl(DatabaseClient client, ExecCallerImpl caller) {
        super(client, caller);
        this.caller = caller;
    }

    private ExecCallerImpl getCaller() {
        return this.caller;
    }

    @Override
    public void call() {
        call(newCallContext());
    }
    @Override
    public void call(CallContext callContext) {
        getCaller().call(getClient(), checkAllowedArgs(callContext));
    }

    @Override
    public BulkExecCaller bulkCaller() {
        return new BulkExecCallerImpl<>(this);
    }
    @Override
    public BulkExecCaller bulkCaller(CallContext callContext) {
        return new BulkExecCallerImpl<>(this, checkAllowedArgs(callContext));
    }
    @Override
    public BulkExecCaller 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 BulkExecCaller 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 BulkExecCallerImpl<>(this, checkAllowedArgs(callContexts[0]));
            default: return new BulkExecCallerImpl<>(this, checkAllowedArgs(callContexts), threadCount);
        }
    }

    public static class BulkExecCallerImpl
            extends IOEndpointImpl.BulkIOEndpointCallerImpl
            implements ExecCaller.BulkExecCaller
    {
        private final ExecEndpointImpl endpoint;
        private ErrorListener errorListener;
        private AtomicInteger aliveCallContextCount;

        public BulkExecCallerImpl(ExecEndpointImpl endpoint) {
            this(endpoint, endpoint.checkAllowedArgs(endpoint.newCallContext()));
        }
        private BulkExecCallerImpl(ExecEndpointImpl endpoint, CallContextImpl callContext) {
            super(endpoint, callContext);
            checkEndpoint(endpoint, "ExecEndpointImpl");
            this.endpoint = endpoint;
        }
        private BulkExecCallerImpl(ExecEndpointImpl endpoint, CallContextImpl[] callContexts, int threadCount) {
            super(endpoint, callContexts, threadCount, threadCount);
            this.endpoint = endpoint;
            this.aliveCallContextCount = new AtomicInteger(threadCount);
        }

        private ExecEndpointImpl getEndpoint() {
            return this.endpoint;
        }

        @Override
        public void awaitCompletion() {
            setPhase(WorkPhase.RUNNING);

            if(getCallContext() != null)
                processOutput();
                // TODO : optimize the case of a single thread with a callContextQueue.

            else if(getCallContextQueue() != null && !getCallContextQueue().isEmpty()){
                try {
                    for (int i = 0; i < getThreadCount(); i++) {
                        BulkCallableImpl bulkCallableImpl = new BulkCallableImpl(this);
                        submitTask(bulkCallableImpl);
                    }
                    if(getCallerThreadPoolExecutor() != null)
                        getCallerThreadPoolExecutor().awaitTermination();
                }
                catch(Throwable throwable) {
                    throw new RuntimeException("Error occurred while awaiting termination ", throwable);
                }
            } else {
                throw new IllegalArgumentException("Cannot process output without Callcontext.");
            }
        }

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

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

        private boolean processExec(CallContextImpl callContext) {
            ErrorDisposition error = ErrorDisposition.RETRY;

            retry:
            for (int retryCount = 0; retryCount < DEFAULT_MAX_RETRIES && error == ErrorDisposition.RETRY; retryCount++) {
                Throwable throwable = null;
                try {
                    logger.trace("exec calling endpoint={} count={} state={}",
                            callContext.getEndpoint().getEndpointPath(), getCallCount(), callContext.getEndpointState());
                    boolean hasState = getEndpoint().getCaller().call(callContext.getClient(), callContext);
                    incrementCallCount();
                    return hasState;
                } catch(Throwable catchedThrowable) {
                    // TODO: logging
                    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);
                            } else {
                                error = ErrorDisposition.SKIP_CALL; //used to be STOP_ALL_CALLS
                            }
                        } catch (Throwable throwable1) {
                            logger.error("Error Listener failed with " , throwable1);
                            error = ErrorDisposition.STOP_ALL_CALLS;
                        }

                        switch (error) {
                            case RETRY:
                                continue retry;
                            case SKIP_CALL:
                                if(callContext.getEndpoint().allowsEndpointState()) {
                                    callContext.withEndpointState(null);
                                }
                                break retry;
                            case STOP_ALL_CALLS:
                                if (getCallerThreadPoolExecutor() != null) {
                                    getCallerThreadPoolExecutor().shutdown();
                                }
                                break retry;
                        }
                    }
                }
            }
            return false;
        }

        private void processOutput() {
            CallContextImpl callContext = getCallContext();
            if(callContext != null) {
                while (processOutput(callContext));
            }
        }

        private boolean processOutput(CallContextImpl callContext){
            boolean continueCalling = processExec(callContext);

            switch(getPhase()) {
                case INTERRUPTING:
                    setPhase(WorkPhase.INTERRUPTED);
                    logger.info("exec interrupted endpoint={} count={} work={}",
                            callContext.getEndpoint().getEndpointPath(), getCallCount(), callContext.getEndpointConstants());
                    return false;
                case RUNNING:
                    if (!continueCalling) {
                        if(getCallerThreadPoolExecutor() == null || aliveCallContextCount.get() == 0)
                            setPhase(WorkPhase.COMPLETED);
                        logger.info("exec completed endpoint={} count={} work={}",
                                callContext.getEndpoint().getEndpointPath(), getCallCount(), callContext.getEndpointConstants());
                        return false;
                    }
                    return true;
                default:
                    throw new MarkLogicInternalException(
                            "unexpected state for "+callContext.getEndpoint().getEndpointPath()+" during loop: "+getPhase().name()
                    );
            }
        }

        static private class BulkCallableImpl implements Callable {
            private final BulkExecCallerImpl bulkExecCallerImpl;

            BulkCallableImpl(BulkExecCallerImpl bulkExecCallerImpl) {
                this.bulkExecCallerImpl = bulkExecCallerImpl;
            }

            @Override
            public Boolean call() throws InterruptedException{
                CallContextImpl callContext = bulkExecCallerImpl.getCallContextQueue().poll();

                boolean continueCalling = (callContext == null) ? false : bulkExecCallerImpl.processOutput(callContext);

                if(continueCalling) {
                    bulkExecCallerImpl.getCallContextQueue().put(callContext);
                    bulkExecCallerImpl.submitTask(this);
                }
                else {
                    if (bulkExecCallerImpl.aliveCallContextCount.decrementAndGet() == 0 && bulkExecCallerImpl.getCallerThreadPoolExecutor() != null) {
                        bulkExecCallerImpl.getCallerThreadPoolExecutor().shutdown();
                    }
                }

                return true;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy