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

com.arangodb.internal.net.Communication Maven / Gradle / Ivy

There is a newer version: 7.16.0
Show newest version
package com.arangodb.internal.net;

import com.arangodb.ArangoDBException;
import com.arangodb.arch.UsedInApi;
import com.arangodb.config.HostDescription;
import com.arangodb.internal.InternalRequest;
import com.arangodb.internal.InternalResponse;
import com.arangodb.internal.RequestType;
import com.arangodb.internal.config.ArangoConfig;
import com.arangodb.internal.serde.InternalSerde;
import com.arangodb.internal.util.HostUtils;
import com.arangodb.internal.util.RequestUtils;
import com.arangodb.internal.util.ResponseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

@UsedInApi
public abstract class Communication implements Closeable {
    private static final Logger LOGGER = LoggerFactory.getLogger(Communication.class);
    protected final HostHandler hostHandler;
    protected final InternalSerde serde;
    private final AtomicLong reqCount;


    protected Communication(final ArangoConfig config, final HostHandler hostHandler) {
        this.hostHandler = hostHandler;
        serde = config.getInternalSerde();
        reqCount = new AtomicLong();
    }

    protected abstract void connect(final Connection conn) throws IOException;

    @Override
    public void close() throws IOException {
        hostHandler.close();
    }

    public CompletableFuture executeAsync(final InternalRequest request, final HostHandle hostHandle) {
        return executeAsync(request, hostHandle, hostHandler.get(hostHandle, RequestUtils.determineAccessType(request)), 0);
    }

    private CompletableFuture executeAsync(final InternalRequest request, final HostHandle hostHandle, final Host host, final int attemptCount) {
        long reqId = reqCount.getAndIncrement();
        return doExecuteAsync(request, hostHandle, host, attemptCount, host.connection(), reqId);
    }

    private CompletableFuture doExecuteAsync(
            final InternalRequest request, final HostHandle hostHandle, final Host host, final int attemptCount, Connection connection, long reqId
    ) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Send Request [id={}]: {} {}", reqId, request, serde.toJsonString(request.getBody()));
        }
        final CompletableFuture rfuture = new CompletableFuture<>();
        try {
            connect(connection);
        } catch (IOException e) {
            handleException(true, e, hostHandle, request, host, reqId, attemptCount, rfuture);
            return rfuture;
        }

        connection.executeAsync(request)
                .whenComplete((response, e) -> {
                    try {
                        if (e instanceof SocketTimeoutException) {
                            // SocketTimeoutException exceptions are wrapped and rethrown.
                            TimeoutException te = new TimeoutException(e.getMessage());
                            te.initCause(e);
                            rfuture.completeExceptionally(ArangoDBException.of(te, reqId));
                        } else if (e instanceof TimeoutException) {
                            rfuture.completeExceptionally(ArangoDBException.of(e, reqId));
                        } else if (e instanceof ConnectException) {
                            handleException(true, e, hostHandle, request, host, reqId, attemptCount, rfuture);
                        } else if (e != null) {
                            handleException(isSafe(request), e, hostHandle, request, host, reqId, attemptCount, rfuture);
                        } else {
                            if (LOGGER.isDebugEnabled()) {
                                LOGGER.debug("Received Response [id={}]: {} {}", reqId, response, serde.toJsonString(response.getBody()));
                            }
                            ArangoDBException errorEntityEx = ResponseUtils.translateError(serde, response);
                            if (errorEntityEx instanceof ArangoDBRedirectException) {
                                if (attemptCount >= 3) {
                                    rfuture.completeExceptionally(errorEntityEx);
                                } else {
                                    final String location = ((ArangoDBRedirectException) errorEntityEx).getLocation();
                                    final HostDescription redirectHost = HostUtils.createFromLocation(location);
                                    hostHandler.failIfNotMatch(redirectHost, errorEntityEx);
                                    mirror(
                                            executeAsync(request, new HostHandle().setHost(redirectHost), hostHandler.get(hostHandle, RequestUtils.determineAccessType(request)), attemptCount + 1),
                                            rfuture
                                    );
                                }
                            } else if (errorEntityEx instanceof ArangoDBUnavailableException) {
                                handleException(true, errorEntityEx, hostHandle, request, host, reqId, attemptCount, rfuture);
                            } else if (errorEntityEx != null) {
                                rfuture.completeExceptionally(errorEntityEx);
                            } else {
                                hostHandler.success();
                                rfuture.complete(response);
                            }
                        }
                    } catch (Exception ex) {
                        rfuture.completeExceptionally(ArangoDBException.of(ex, reqId));
                    }
                });
        return rfuture;
    }

    private void handleException(boolean isSafe, Throwable e, HostHandle hostHandle, InternalRequest request, Host host,
                                 long reqId, int attemptCount, CompletableFuture rfuture) {
        IOException ioEx = wrapIOEx(e);
        hostHandler.fail(ioEx);
        if (hostHandle != null && hostHandle.getHost() != null) {
            hostHandle.setHost(null);
        }
        hostHandler.checkNext(hostHandle, RequestUtils.determineAccessType(request));
        if (isSafe) {
            Host nextHost = hostHandler.get(hostHandle, RequestUtils.determineAccessType(request));
            LOGGER.warn("Could not connect to {} while executing request [id={}]",
                    host.getDescription(), reqId, ioEx);
            LOGGER.debug("Try connecting to {}", nextHost.getDescription());
            mirror(
                    executeAsync(request, hostHandle, nextHost, attemptCount),
                    rfuture
            );
        } else {
            ArangoDBException aEx = ArangoDBException.of(ioEx, reqId);
            rfuture.completeExceptionally(aEx);
        }
    }

    private void mirror(CompletableFuture up, CompletableFuture down) {
        up.whenComplete((v, err) -> {
            if (err != null) {
                down.completeExceptionally(err instanceof CompletionException ? err.getCause() : err);
            } else {
                down.complete(v);
            }
        });
    }

    private static IOException wrapIOEx(Throwable t) {
        if (t instanceof IOException) {
            return (IOException) t;
        } else {
            return new IOException(t);
        }
    }

    private boolean isSafe(final InternalRequest request) {
        RequestType type = request.getRequestType();
        return type == RequestType.GET || type == RequestType.HEAD || type == RequestType.OPTIONS;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy