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

dev.vality.woody.thrift.impl.http.THClientBuilder Maven / Gradle / Ivy

package dev.vality.woody.thrift.impl.http;

import dev.vality.woody.api.AbstractClientBuilder;
import dev.vality.woody.api.event.ClientEventListener;
import dev.vality.woody.api.event.CompositeClientEventListener;
import dev.vality.woody.api.flow.WFlow;
import dev.vality.woody.api.flow.error.ErrorMapProcessor;
import dev.vality.woody.api.flow.error.WErrorDefinition;
import dev.vality.woody.api.flow.error.WErrorMapper;
import dev.vality.woody.api.generator.IdGenerator;
import dev.vality.woody.api.interceptor.CommonInterceptor;
import dev.vality.woody.api.interceptor.CompositeInterceptor;
import dev.vality.woody.api.interceptor.ContainerCommonInterceptor;
import dev.vality.woody.api.interceptor.ext.ExtensionBundle;
import dev.vality.woody.api.provider.ProviderEventInterceptor;
import dev.vality.woody.api.proxy.InvocationTargetProvider;
import dev.vality.woody.api.trace.ContextSpan;
import dev.vality.woody.api.trace.context.TraceContext;
import dev.vality.woody.api.trace.context.metadata.MetadataExtensionKit;
import dev.vality.woody.api.transport.TransportEventInterceptor;
import dev.vality.woody.thrift.impl.http.error.THErrorMapProcessor;
import dev.vality.woody.thrift.impl.http.event.THCEventLogListener;
import dev.vality.woody.thrift.impl.http.event.THClientEvent;
import dev.vality.woody.thrift.impl.http.interceptor.THMessageInterceptor;
import dev.vality.woody.thrift.impl.http.interceptor.THTransportInterceptor;
import dev.vality.woody.thrift.impl.http.interceptor.ext.MetadataExtensionBundle;
import io.opentelemetry.sdk.resources.Resource;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransport;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;

public class THClientBuilder extends AbstractClientBuilder {

    private HttpClient httpClient;
    private WErrorMapper errorMapper;
    private List metadataExtensionKits;
    private boolean logEnabled = true;
    private final THCEventLogListener logListener = new THCEventLogListener();

    public THClientBuilder() {
        super.withIdGenerator(WFlow.createDefaultIdGenerator());
    }

    protected static  T createThriftClient(Class clientIface, TProtocol tProtocol) {
        try {
            Optional clientClass = Arrays.stream(clientIface.getDeclaringClass().getClasses())
                    .filter(cl -> cl.getSimpleName().equals("Client")).findFirst();
            if (!clientClass.isPresent()) {
                throw new IllegalArgumentException(
                        "Client interface doesn't conform to Thrift generated class structure");
            }
            if (!TServiceClient.class.isAssignableFrom(clientClass.get())) {
                throw new IllegalArgumentException("Client class doesn't conform to Thrift generated class structure");
            }
            if (!clientIface.isAssignableFrom(clientClass.get())) {
                throw new IllegalArgumentException(
                        "Client class has wrong type which is not assignable to client interface");
            }
            Constructor constructor = clientClass.get().getConstructor(TProtocol.class);
            if (constructor == null) {
                throw new IllegalArgumentException("Client class doesn't have required constructor to be created");
            }
            TServiceClient tClient = (TServiceClient) constructor.newInstance(tProtocol);
            return (T) tClient;
        } catch (NoSuchMethodException |
                InstantiationException |
                IllegalAccessException |
                InvocationTargetException e) {
            throw new IllegalArgumentException("Failed to createCtxBundle provider client", e);
        }
    }

    public THClientBuilder withErrorMapper(WErrorMapper errorMapper) {
        this.errorMapper = errorMapper;
        return this;
    }

    public THClientBuilder withHttpClient(HttpClient httpClient) {
        this.httpClient = httpClient;
        return this;
    }

    public THClientBuilder withMetaExtensions(List extensionKits) {
        this.metadataExtensionKits = extensionKits;
        return this;
    }

    public THClientBuilder withNetworkTimeout(int timeout) {
        super.withNetworkTimeout(timeout);
        return this;
    }

    public THClientBuilder withLogEnabled(boolean enabled) {
        this.logEnabled = enabled;
        return this;
    }

    @Override
    public THClientBuilder withAddress(URI address) {
        return (THClientBuilder) super.withAddress(address);
    }

    @Override
    public THClientBuilder withEventListener(ClientEventListener listener) {
        return (THClientBuilder) super.withEventListener(listener);
    }

    @Override
    public THClientBuilder withIdGenerator(IdGenerator generator) {
        return (THClientBuilder) super.withIdGenerator(generator);
    }

    public boolean isCustomHttpClient() {
        return httpClient != null;
    }

    public HttpClient getHttpClient() {
        if (isCustomHttpClient()) {
            return httpClient;
        } else {
            return createHttpClient();
        }

    }

    @Override
    protected  T build(Class iface, InvocationTargetProvider targetProvider) {
        if (logEnabled) {
            ClientEventListener listener = getEventListener();
            listener = (listener == null || listener == DEFAULT_EVENT_LISTENER) ? logListener :
                    new CompositeClientEventListener(logListener, listener);
            withEventListener(listener);
        }
        return super.build(iface, targetProvider);
    }

    public void destroy() {
    }

    @Override
    protected BiConsumer getErrorDefinitionConsumer() {
        return (eDef, contextSpan) -> {
        };
    }

    @Override
    protected Runnable getErrorListener() {
        return createEventRunnable(getEventListener());
    }

    @Override
    protected Runnable getOnCallStartEventListener() {
        return createEventRunnable(getEventListener());
    }

    @Override
    protected Runnable getOnCallEndEventListener() {
        return createEventRunnable(getEventListener());
    }

    @Override
    protected Runnable getOnSendEventListener() {
        return createEventRunnable(getEventListener());
    }

    @Override
    protected Runnable getOnReceiveEventListener() {
        return createEventRunnable(getEventListener());
    }

    @Override
    protected ErrorMapProcessor createErrorMapProcessor(Class iface) {
        return THErrorMapProcessor.getInstance(true, iface, errorMapper);
    }

    @Override
    protected  T createProviderClient(Class iface) {
        try {
            THttpClient tHttpClient =
                    new THttpClient(getAddress().toString(), getHttpClient(), createTransportInterceptor());
            tHttpClient.setReadTimeout(getNetworkTimeout());
            TProtocol tProtocol = createProtocol(tHttpClient);
            return createThriftClient(iface, tProtocol);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected void destroyProviderClient(Object client, boolean customClient) {
        if (!customClient && client instanceof TServiceClient) {
            TTransport tTransport = ((TServiceClient) client).getInputProtocol().getTransport();
            if (tTransport instanceof THttpClient) {
                HttpClient httpClient = ((THttpClient) tTransport).getHttpClient();
                if (httpClient instanceof CloseableHttpClient) {
                    try {
                        ((CloseableHttpClient) httpClient).close();
                    } catch (IOException e) {
                        throw new RuntimeException("Failed to release HttpClient", e);
                    }
                }
            }

        }
    }

    protected TProtocolFactory createTransferProtocolFactory() {
        return new TBinaryProtocol.Factory();
    }

    protected TProtocol createProtocol(TTransport tTransport) {
        return BuilderUtils.wrapProtocolFactory(createTransferProtocolFactory(), createMessageInterceptor(), true)
                .getProtocol(tTransport);
    }

    protected HttpClient createHttpClient() {
        return HttpClients.createMinimal(new BasicHttpClientConnectionManager());
    }

    protected CommonInterceptor createMessageInterceptor() {
        return new CompositeInterceptor(new ContainerCommonInterceptor(new THMessageInterceptor(true, true),
                new THMessageInterceptor(true, false)),
                new ProviderEventInterceptor(getOnCallStartEventListener(), null));
    }

    protected CommonInterceptor createTransportInterceptor() {
        List extensionBundles = Arrays.asList(new MetadataExtensionBundle(
                metadataExtensionKits == null ? Collections.emptyList() : metadataExtensionKits));
        return new CompositeInterceptor(
                new ContainerCommonInterceptor(new THTransportInterceptor(extensionBundles, true, true),
                        new THTransportInterceptor(extensionBundles, true, false)),
                new TransportEventInterceptor(getOnSendEventListener(), getOnReceiveEventListener(), null));
    }

    private Runnable createEventRunnable(ClientEventListener eventListener) {
        return () -> eventListener.notifyEvent(new THClientEvent(TraceContext.getCurrentTraceData()));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy