/* * Copyright 2012-2015, the original author or authors. * * 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 com.flipkart.phantom.task.impl; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.flipkart.phantom.event.ServiceProxyEvent; import com.flipkart.phantom.task.spi.Decoder; import com.flipkart.phantom.task.spi.Executor; import com.flipkart.phantom.task.spi.TaskContext; import com.flipkart.phantom.task.spi.TaskRequestWrapper; import com.flipkart.phantom.task.spi.TaskResult; import com.flipkart.phantom.task.spi.interceptor.RequestInterceptor; import com.flipkart.phantom.task.spi.interceptor.ResponseInterceptor; import com.github.kristofa.brave.Brave; import com.google.common.base.Optional; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; /** * <code>TaskHandlerExecutor</code> is an extension of {@link HystrixCommand}. It is essentially a * wrapper around {@link TaskHandler}, providing a means for the TaskHandler to to be called using * a Hystrix Command. * * @author devashishshankar * @version 1.0, 19th March, 2013 */ @SuppressWarnings("rawtypes") public class TaskHandlerExecutor<S> extends HystrixCommand<TaskResult> implements Executor<TaskRequestWrapper<S>, TaskResult> { /** TaskResult message constants */ public static final String NO_RESULT = "The command returned no result"; public static final String ASYNC_QUEUED = "The command dispatched for async execution"; /** The default Hystrix group to which the command belongs, unless otherwise mentioned*/ public static final String DEFAULT_HYSTRIX_GROUP = "DEFAULT_GROUP"; /** The default Hystrix Thread pool to which this command belongs, unless otherwise mentioned */ public static final String DEFAULT_HYSTRIX_THREAD_POOL = "DEFAULT_THREAD_POOL"; /** The default Hystrix Thread pool to which this command belongs, unless otherwise mentioned */ public static final int DEFAULT_HYSTRIX_THREAD_POOL_SIZE = 10; /** Event Type for publishing all events which are generated here */ private final static String COMMAND_HANDLER = "COMMAND_HANDLER"; /** The {@link TaskHandler} or {@link HystrixTaskHandler} instance which this Command wraps around */ protected TaskHandler taskHandler; /** The params required to execute a TaskHandler */ protected TaskContext taskContext; /** The command for which this task is executed */ protected String command; /** The parameters which are utilized by the task for execution */ protected Map<String, Object> params; /** Data which is utilized by the task for execution */ protected S data; /* Task Request Wrapper */ protected TaskRequestWrapper<S> taskRequestWrapper; /* Decoder to decode requests */ protected Decoder decoder; /** Event which records various paramenters of this request execution & published later */ protected ServiceProxyEvent.Builder eventBuilder; /** List of request and response interceptors */ private List<RequestInterceptor<TaskRequestWrapper<S>>> requestInterceptors = new LinkedList<RequestInterceptor<TaskRequestWrapper<S>>>(); private List<ResponseInterceptor<TaskResult>> responseInterceptors = new LinkedList<ResponseInterceptor<TaskResult>>(); /** * Basic constructor for {@link TaskHandler}. The Hystrix command name is commandName. The group name is the Handler Name * (HystrixTaskHandler#getName) * * @param taskHandler The taskHandler to be wrapped * @param taskContext The context (Unique context required by Handlers to communicate with the container.) * @param commandName name of the command * @param timeout the timeout for the Hystrix thread * @param threadPoolName Name of the thread pool * @param coreThreadPoolSize core size of the thread pool * @param maxThreadPoolSize max size of the thread pool * @param taskRequestWrapper requestWrapper containing the data and the parameters */ protected TaskHandlerExecutor(TaskHandler taskHandler, TaskContext taskContext, String commandName, int timeout, String threadPoolName, int coreThreadPoolSize, int maxThreadPoolSize, TaskRequestWrapper<S> taskRequestWrapper ) { this(taskHandler, taskContext, commandName, timeout, threadPoolName, coreThreadPoolSize, maxThreadPoolSize, taskRequestWrapper, null); } /** * Basic constructor for {@link TaskHandler}. The Hystrix command name is commandName. The group name is the Handler Name * (HystrixTaskHandler#getName) * * @param taskHandler The taskHandler to be wrapped * @param taskContext The context (Unique context required by Handlers to communicate with the container.) * @param commandName name of the command * @param timeout the timeout for the Hystrix thread * @param threadPoolName Name of the thread pool * @param coreThreadPoolSize core size of the thread pool * @param maxThreadPoolSize max size of the thread pool * @param taskRequestWrapper requestWrapper containing the data and the parameters * @param decoder Decoder sent by the Client */ protected TaskHandlerExecutor(TaskHandler taskHandler, TaskContext taskContext, String commandName, int timeout, String threadPoolName, int coreThreadPoolSize, int maxThreadPoolSize, TaskRequestWrapper<S> taskRequestWrapper , Decoder decoder ) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(taskHandler.getName())) .andCommandKey(HystrixCommandKey.Factory.asKey(commandName)) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(taskHandler.getVersionedThreadPoolName(threadPoolName))) .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withAllowMaximumSizeToDivergeFromCoreSize(true).withCoreSize(coreThreadPoolSize >= 0 ? coreThreadPoolSize : maxThreadPoolSize).withMaximumSize(maxThreadPoolSize)) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(timeout))); this.taskHandler = taskHandler; this.taskContext = taskContext; this.command = commandName; this.data = taskRequestWrapper.getData(); this.params = taskRequestWrapper.getParams(); this.taskRequestWrapper = taskRequestWrapper; this.eventBuilder = new ServiceProxyEvent.Builder(commandName, COMMAND_HANDLER); this.decoder = decoder; } /** * Constructor for {@link TaskHandler} using Semaphore isolation. The Hystrix command name is commandName. The group name is the Handler Name * (HystrixTaskHandler#getName) * * @param taskHandler The taskHandler to be wrapped * @param taskContext The context (Unique context required by Handlers to communicate with the container.) * @param commandName name of the command * @param taskRequestWrapper requestWrapper containing the data and the parameters * @param concurrentRequestSize no of Max Concurrent requests which can be served */ protected TaskHandlerExecutor(TaskHandler taskHandler, TaskContext taskContext, String commandName, TaskRequestWrapper<S> taskRequestWrapper , int concurrentRequestSize ) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(taskHandler.getName())) .andCommandKey(HystrixCommandKey.Factory.asKey(commandName)) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE). withExecutionIsolationSemaphoreMaxConcurrentRequests(concurrentRequestSize))) ; this.taskHandler = taskHandler; this.taskContext = taskContext; this.command = commandName; this.data = taskRequestWrapper.getData(); this.params = taskRequestWrapper.getParams(); this.taskRequestWrapper = taskRequestWrapper; this.eventBuilder = new ServiceProxyEvent.Builder(commandName, COMMAND_HANDLER); } /** * Constructor for {@link TaskHandler} using Semaphore isolation. The Hystrix command name is commandName. The group name is the Handler Name * (HystrixTaskHandler#getName) * * @param taskHandler The taskHandler to be wrapped * @param taskContext The context (Unique context required by Handlers to communicate with the container.) * @param commandName name of the command * @param taskRequestWrapper requestWrapper containing the data and the parameters * @param decoder Decoder sent by the Client * @param concurrentRequestSize no of Max Concurrent requests which can be served */ protected TaskHandlerExecutor(TaskHandler taskHandler, TaskContext taskContext, String commandName, TaskRequestWrapper<S> taskRequestWrapper , int concurrentRequestSize, Decoder decoder ) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(taskHandler.getName())) .andCommandKey(HystrixCommandKey.Factory.asKey(commandName)) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE). withExecutionIsolationSemaphoreMaxConcurrentRequests(concurrentRequestSize))) ; this.taskHandler = taskHandler; this.taskContext = taskContext; this.command = commandName; this.data = taskRequestWrapper.getData(); this.params = taskRequestWrapper.getParams(); this.taskRequestWrapper = taskRequestWrapper; this.decoder = decoder; this.eventBuilder = new ServiceProxyEvent.Builder(commandName, COMMAND_HANDLER); } /** * Constructor for TaskHandlerExecutor run through Default Hystrix Thread Pool ({@link TaskHandlerExecutor#DEFAULT_HYSTRIX_THREAD_POOL}) * @param taskHandler The taskHandler to be wrapped * @param taskContext The context (Unique context required by Handlers to communicate with the container.) * @param commandName name of the command * @param executorTimeout the timeout for the Hystrix thread * @param taskRequestWrapper requestWrapper containing the data and the parameters */ public TaskHandlerExecutor(TaskHandler taskHandler, TaskContext taskContext, String commandName, int executorTimeout, TaskRequestWrapper<S> taskRequestWrapper) { this(taskHandler,taskContext,commandName,executorTimeout, DEFAULT_HYSTRIX_THREAD_POOL, DEFAULT_HYSTRIX_THREAD_POOL_SIZE,DEFAULT_HYSTRIX_THREAD_POOL_SIZE,taskRequestWrapper); } /** * Constructor for TaskHandlerExecutor run through Default Hystrix Thread Pool ({@link TaskHandlerExecutor#DEFAULT_HYSTRIX_THREAD_POOL}) * @param taskHandler The taskHandler to be wrapped * @param taskContext The context (Unique context required by Handlers to communicate with the container.) * @param commandName name of the command * @param executorTimeout the timeout for the Hystrix thread * @param decoder Decoder sent by the Client * @param taskRequestWrapper requestWrapper containing the data and the parameters */ public TaskHandlerExecutor(TaskHandler taskHandler, TaskContext taskContext, String commandName, int executorTimeout, TaskRequestWrapper<S> taskRequestWrapper, Decoder decoder) { this(taskHandler,taskContext,commandName,executorTimeout, DEFAULT_HYSTRIX_THREAD_POOL, DEFAULT_HYSTRIX_THREAD_POOL_SIZE, DEFAULT_HYSTRIX_THREAD_POOL_SIZE,taskRequestWrapper, decoder); } /** * Interface method implementation. @see HystrixCommand#run() * If Decoder has not been set by the Client, it goes into the default Implementation, otherwise * It calls the execute Method which handles decoder. * @throws Exception */ @SuppressWarnings("unchecked") @Override protected TaskResult run() throws Exception { this.eventBuilder.withRequestExecutionStartTime(System.currentTimeMillis()); if (this.taskRequestWrapper.getRequestContext().isPresent() && this.taskRequestWrapper.getRequestContext().get().getCurrentServerSpan() != null) { Brave.getServerSpanThreadBinder().setCurrentSpan(this.taskRequestWrapper.getRequestContext().get().getCurrentServerSpan()); } for (RequestInterceptor<TaskRequestWrapper<S>> requestInterceptor : this.requestInterceptors) { requestInterceptor.process(this.taskRequestWrapper); } Optional<RuntimeException> transportException = Optional.absent(); TaskResult result = null; try { if(decoder == null) { result = this.taskHandler.execute(taskContext, command, params, data); } else { result = this.taskHandler.execute(taskContext, command, taskRequestWrapper,decoder); } if (result == null) { result = new TaskResult<byte[]>(true, TaskHandlerExecutor.NO_RESULT); } } catch (RuntimeException e) { transportException = Optional.of(e); throw e; // rethrow this for it to handled by other layers in the call stack } finally { // signal to the handler to release resources if the command timed out if (this.isResponseTimedOut()) { if(result!= null ) { if (HystrixTaskHandler.class.isAssignableFrom(this.taskHandler.getClass())) { ((HystrixTaskHandler)this.taskHandler).releaseResources(result); } } } for (ResponseInterceptor<TaskResult> responseInterceptor : this.responseInterceptors) { responseInterceptor.process(result, transportException); } } if (!result.isSuccess()) { throw new RuntimeException("Command returned FALSE: " + result.getMessage()); } return result; } /** * Interface method implementation. @see HystrixCommand#getFallback() */ @SuppressWarnings("unchecked") @Override protected TaskResult getFallback() { // check and populate execution error root cause, if any, for use in fallback if (this.isFailedExecution()) { this.params.put(Executor.EXECUTION_ERROR_CAUSE, this.getFailedExecutionException()); } if(this.taskHandler instanceof HystrixTaskHandler) { HystrixTaskHandler hystrixTaskHandler = (HystrixTaskHandler) this.taskHandler; if(decoder == null) { return hystrixTaskHandler.getFallBack(taskContext, command, params, data); } else { return hystrixTaskHandler.getFallBack(taskContext, command, taskRequestWrapper, decoder); } } return null; } /** * Interface method implementation. Adds the RequestInterceptor to the list of request interceptors that will be invoked * @see com.flipkart.phantom.task.spi.Executor#addRequestInterceptor(com.flipkart.phantom.task.spi.interceptor.RequestInterceptor) */ public void addRequestInterceptor(RequestInterceptor<TaskRequestWrapper<S>> requestInterceptor) { this.requestInterceptors.add(requestInterceptor); } /** * Interface method implementation. Adds the ResponseInterceptor to the list of response interceptors that will be invoked * @see com.flipkart.phantom.task.spi.Executor#addResponseInterceptor(com.flipkart.phantom.task.spi.interceptor.ResponseInterceptor) */ public void addResponseInterceptor(ResponseInterceptor<TaskResult> responseInterceptor){ this.responseInterceptors.add(responseInterceptor); } /** * Interface method implementation. Returns the name of the TaskHandler used by this Executor * @see com.flipkart.phantom.task.spi.Executor#getServiceName() */ public Optional<String> getServiceName() { return Optional.of(this.taskHandler.getName()); } /** * Interface method implementation. Returns the TaskRequestWrapper instance that this Executor was created with * @see com.flipkart.phantom.task.spi.Executor#getRequestWrapper() */ public TaskRequestWrapper<S> getRequestWrapper() { return this.taskRequestWrapper; } /** * First It checks the call invocation type has been overridden for the command, * if not, it defaults to task handler call invocation type. * * @return callInvocation Type */ public int getCallInvocationType() { if(this.taskHandler.getCallInvocationTypePerCommand() != null) { Integer callInvocationType = this.taskHandler.getCallInvocationTypePerCommand().get(this.command); if(callInvocationType != null) { return callInvocationType; } } return this.taskHandler.getCallInvocationType(); } /** * Getter method for the event builder */ public ServiceProxyEvent.Builder getEventBuilder() { return eventBuilder; } }