io.dropwizard.revolver.core.RevolverCommand.java Source code

Java tutorial

Introduction

Here is the source code for io.dropwizard.revolver.core.RevolverCommand.java

Source

/*
 * Copyright 2016 Phaneesh Nagaraja <phaneesh.n@gmail.com>.
 *
 *  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 io.dropwizard.revolver.core;

import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.netflix.hystrix.HystrixCommand;
import io.dropwizard.revolver.core.config.ClientConfig;
import io.dropwizard.revolver.core.config.CommandHandlerConfig;
import io.dropwizard.revolver.core.config.RevolverServiceConfig;
import io.dropwizard.revolver.core.config.RuntimeConfig;
import io.dropwizard.revolver.core.model.RevolverRequest;
import io.dropwizard.revolver.core.model.RevolverResponse;
import io.dropwizard.revolver.core.tracing.Trace;
import io.dropwizard.revolver.core.tracing.TraceCollector;
import io.dropwizard.revolver.core.tracing.TraceInfo;
import io.dropwizard.revolver.core.util.RevolverCommandHelper;
import io.dropwizard.revolver.core.util.RevolverExceptionHelper;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.MDC;

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author phaneesh
 */
@Slf4j
public abstract class RevolverCommand<RequestType extends RevolverRequest, ResponseType extends RevolverResponse, ContextType extends RevolverContext, ServiceConfigurationType extends RevolverServiceConfig, CommandHandlerConfigType extends CommandHandlerConfig> {

    private final ContextType context;
    private final RuntimeConfig runtimeConfig;
    private final ServiceConfigurationType serviceConfiguration;
    private final Map<String, CommandHandlerConfigType> apiConfigurations;
    private final TraceCollector traceCollector;
    private ClientConfig clientConfiguration;

    public RevolverCommand(final ContextType context, final ClientConfig clientConfiguration,
            final RuntimeConfig runtimeConfig, final ServiceConfigurationType serviceConfiguration,
            final Map<String, CommandHandlerConfigType> apiConfigurations, final TraceCollector traceCollector) {
        this.context = context;
        this.clientConfiguration = clientConfiguration;
        this.runtimeConfig = runtimeConfig;
        this.serviceConfiguration = serviceConfiguration;
        this.apiConfigurations = apiConfigurations;
        this.traceCollector = traceCollector;
    }

    @SuppressWarnings("unchecked")
    public ResponseType execute(final RequestType request) throws RevolverExecutionException, TimeoutException {
        final CommandHandlerConfigType apiConfiguration = this.apiConfigurations.get(request.getApi());
        if (null == apiConfiguration) {
            throw new RevolverExecutionException(RevolverExecutionException.Type.BAD_REQUEST,
                    "No api spec defined for key: " + request.getApi());
        }
        final RequestType normalizedRequest = RevolverCommandHelper.normalize(request);
        final TraceInfo traceInfo = normalizedRequest.getTrace();
        addContextInfo(request, traceInfo);
        final Stopwatch watch = Stopwatch.createStarted();
        String errorMessage = null;
        try {
            ResponseType response = (ResponseType) new RevolverCommandHandler(
                    RevolverCommandHelper.setter(this, request.getApi()), this.context, this, normalizedRequest)
                            .execute();
            log.debug("Command response: " + response);
            return response;
        } catch (Throwable t) {
            val rootCause = ExceptionUtils.getRootCause(t);
            if (rootCause instanceof TimeoutException) {
                throw (TimeoutException) rootCause;
            }
            errorMessage = rootCause.getLocalizedMessage();
            throw new RevolverExecutionException(RevolverExecutionException.Type.SERVICE_ERROR, rootCause);
        } finally {
            publishTrace(Trace.builder().caller(this.clientConfiguration.getClientName())
                    .service(this.serviceConfiguration.getService()).api(apiConfiguration.getApi())
                    .duration(watch.stop().elapsed(TimeUnit.MILLISECONDS))
                    .transactionId(traceInfo.getTransactionId()).requestId(traceInfo.getRequestId())
                    .parentRequestId(traceInfo.getParentRequestId()).timestamp(traceInfo.getTimestamp())
                    .attributes(traceInfo.getAttributes()).error(!Strings.isNullOrEmpty(errorMessage))
                    .errorReason(errorMessage).build());
        }
    }

    private void addContextInfo(RequestType request, TraceInfo traceInfo) {
        MDC.put("command", RevolverCommandHelper.getName(request));
        MDC.put("transactionId", traceInfo.getTransactionId());
        MDC.put("requestId", traceInfo.getRequestId());
        MDC.put("parentRequestId", traceInfo.getParentRequestId());
    }

    @SuppressWarnings("unchecked")
    public CompletableFuture<ResponseType> executeAsync(final RequestType request) {
        final RequestType normalizedRequest = RevolverCommandHelper.normalize(request);
        final TraceInfo traceInfo = normalizedRequest.getTrace();
        addContextInfo(request, traceInfo);
        final Stopwatch watch = Stopwatch.createStarted();
        final Future<ResponseType> responseFuture = new RevolverCommandHandler(
                RevolverCommandHelper.setter(this, request.getApi()), this.context, this, normalizedRequest)
                        .queue();
        return CompletableFuture.supplyAsync(() -> {
            String errorMessage = null;
            try {
                return responseFuture.get();
            } catch (Throwable t) {
                errorMessage = RevolverExceptionHelper.getLeafErrorMessage(t);
                throw new RevolverExecutionException(RevolverExecutionException.Type.SERVICE_ERROR,
                        String.format("Error executing command %s", RevolverCommandHelper.getName(request)),
                        RevolverExceptionHelper.getLeafThrowable(t));
            } finally {
                publishTrace(Trace.builder().caller(this.clientConfiguration.getClientName())
                        .service(this.serviceConfiguration.getService()).api(request.getApi())
                        .duration(watch.stop().elapsed(TimeUnit.MILLISECONDS))
                        .transactionId(traceInfo.getTransactionId()).requestId(traceInfo.getRequestId())
                        .parentRequestId(traceInfo.getParentRequestId()).timestamp(traceInfo.getTimestamp())
                        .attributes(traceInfo.getAttributes()).error(!Strings.isNullOrEmpty(errorMessage))
                        .errorReason(errorMessage).build());
                removeContextInfo();
            }
        });
    }

    private void publishTrace(Trace build) {
        traceCollector.publish(build);
    }

    private void removeContextInfo() {
        MDC.remove("command");
        MDC.remove("requestId");
        MDC.remove("transactionId");
        MDC.remove("parentRequestId");
    }

    public boolean isFallbackEnabled() {
        return true;
    }

    protected abstract ResponseType execute(final ContextType context, final RequestType request) throws Exception;

    protected abstract ResponseType fallback(final ContextType context, final RequestType request);

    protected ClientConfig getClientConfiguration() {
        return clientConfiguration;
    }

    public RuntimeConfig getRuntimeConfig() {
        return this.runtimeConfig;
    }

    public ServiceConfigurationType getServiceConfiguration() {
        return this.serviceConfiguration;
    }

    public Map<String, CommandHandlerConfigType> getApiConfigurations() {
        return this.apiConfigurations;
    }

    private static class RevolverCommandHandler<RequestType extends RevolverRequest, ResponseType extends RevolverResponse, ContextType extends RevolverContext, ServiceConfigurationType extends RevolverServiceConfig, CommandHandlerConfigurationType extends CommandHandlerConfig>
            extends HystrixCommand<ResponseType> {
        private final RevolverCommand<RequestType, ResponseType, ContextType, ServiceConfigurationType, CommandHandlerConfigurationType> handler;
        private final RequestType request;
        private final ContextType context;

        RevolverCommandHandler(final HystrixCommand.Setter setter, final ContextType context,
                final RevolverCommand<RequestType, ResponseType, ContextType, ServiceConfigurationType, CommandHandlerConfigurationType> handler,
                final RequestType request) {
            super(setter);
            this.context = context;
            this.handler = handler;
            this.request = request;
        }

        @Override
        protected ResponseType run() throws Exception {
            return this.handler.execute(this.context, this.request);
        }

        @Override
        protected ResponseType getFallback() {
            return this.handler.fallback(this.context, this.request);
        }
    }

}