/* * 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.Map; import java.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.flipkart.phantom.event.ServiceProxyEvent; import com.flipkart.phantom.event.ServiceProxyEventProducer; import com.flipkart.phantom.task.impl.interceptor.ClientRequestInterceptor; import com.flipkart.phantom.task.impl.interceptor.CommandClientResponseInterceptor; import com.flipkart.phantom.task.impl.registry.TaskHandlerRegistry; import com.flipkart.phantom.task.impl.repository.AbstractExecutorRepository; import com.flipkart.phantom.task.spi.Decoder; import com.flipkart.phantom.task.spi.Executor; import com.flipkart.phantom.task.spi.RequestWrapper; import com.flipkart.phantom.task.spi.TaskRequestWrapper; import com.flipkart.phantom.task.spi.TaskResult; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; /** * <code>TaskHandlerExecutorRepository</code> is a repository that searches for a {@link TaskHandler} * in the {@link TaskHandlerRegistry} based on a command name and then wraps the {@link TaskHandler} into * a {@link TaskHandlerExecutor} and returns it. * * @author devashishshankar * @version 1.0, 20th March, 2013 */ @SuppressWarnings("rawtypes") public class TaskHandlerExecutorRepository extends AbstractExecutorRepository<TaskRequestWrapper,TaskResult, TaskHandler> { /** Logger for this class*/ private static final Logger LOGGER = LoggerFactory.getLogger(TaskHandlerExecutorRepository.class); /** Regex for finding all non alphanumeric characters */ public static final String ONLY_ALPHANUMERIC_REGEX = "[^\\dA-Za-z_]"; /** Regex for finding all whitespace characters */ public static final String WHITESPACE_REGEX = "\\s+"; /** The default thread pool core size*/ private static final int DEFAULT_THREAD_POOL_SIZE = 10; /** The publisher used to broadcast events to Service Proxy Subscribers */ private ServiceProxyEventProducer eventProducer; /** * Gets the TaskHandlerExecutor or RequestCacheableTaskHandlerExecutor for a commandName * @param commandName the command name/String for which the Executor is needed * @param proxyName the threadPool using which command has to be processed * @return The executor corresponding to the commandName. * @throws UnsupportedOperationException if doesn't find a TaskHandler in the registry corresponding to the command name */ public Executor<TaskRequestWrapper,TaskResult> getExecutor(String commandName,String proxyName, TaskRequestWrapper requestWrapper) { Executor<TaskRequestWrapper,TaskResult> executor = null; //Regex matching of threadPoolName and commandName //(Hystrix dashboard requires names to be alphanumeric) String refinedCommandName = this.getRefinedName(commandName); String refinedProxyName = this.getRefinedName(proxyName); TaskHandler taskHandler = this.getTaskHandler(commandName, refinedCommandName, proxyName, refinedProxyName, requestWrapper); int maxConcurrency = this.getMaxConcurrency(taskHandler, proxyName); int minConcurrency = this.getMinConcurrency(taskHandler, proxyName); int executionTimeout = this.getExecutionTimeout(taskHandler, commandName); if(taskHandler instanceof HystrixTaskHandler) { if(((HystrixTaskHandler)taskHandler).getIsolationStrategy()==ExecutionIsolationStrategy.SEMAPHORE) { executor = getTaskHandlerExecutorWithSemaphoreIsolation(requestWrapper, refinedCommandName, taskHandler, maxConcurrency); } else { executor = getTaskHandlerExecutor(requestWrapper, refinedCommandName, refinedProxyName, minConcurrency, maxConcurrency,executionTimeout, taskHandler); } } else { executor = getTaskHandlerExecutor(requestWrapper, refinedCommandName, refinedProxyName, minConcurrency, maxConcurrency, executionTimeout, taskHandler); } return this.wrapExecutorWithInterceptors(executor, taskHandler); } /** * Gets the TaskHandlerExecutor or RequestCacheableTaskHandlerExecutor for a commandName * @param commandName the command name/String for which the Executor is needed * @param proxyName the threadPool using which command has to be processed * @param requestWrapper requestWrapper * @param decoder decoder passed by the client * @return The executor corresponding to the commandName. * @throws UnsupportedOperationException if doesn't find a TaskHandler in the registry corresponding to the command name */ public Executor<TaskRequestWrapper,TaskResult> getExecutor(String commandName,String proxyName, TaskRequestWrapper requestWrapper, Decoder decoder) { Executor<TaskRequestWrapper,TaskResult> executor = null; //Regex matching of threadPoolName and commandName //(Hystrix dashboard requires names to be alphanumeric) String refinedCommandName = this.getRefinedName(commandName); String refinedProxyName = this.getRefinedName(proxyName); TaskHandler taskHandler = this.getTaskHandler(commandName, refinedCommandName, proxyName, refinedProxyName, requestWrapper); int maxConcurrency = this.getMaxConcurrency(taskHandler, proxyName); int minConcurrency = this.getMinConcurrency(taskHandler, proxyName); int executionTimeout = this.getExecutionTimeout(taskHandler, commandName); if(taskHandler instanceof HystrixTaskHandler) { if(((HystrixTaskHandler)taskHandler).getIsolationStrategy()==ExecutionIsolationStrategy.SEMAPHORE) { executor = getTaskHandlerExecutorWithSemaphoreIsolationAndDecoder(requestWrapper, refinedCommandName, taskHandler, maxConcurrency, decoder); } else { executor = getTaskHandlerExecutorWithDecoder(requestWrapper, refinedCommandName, refinedProxyName, minConcurrency, maxConcurrency,executionTimeout, taskHandler, decoder); } } else { executor = getTaskHandlerExecutorWithDecoder(requestWrapper, refinedCommandName, refinedProxyName, minConcurrency, maxConcurrency, executionTimeout, taskHandler, decoder); } return this.wrapExecutorWithInterceptors(executor, taskHandler); } /** * Gets the TaskHandlerExecutor for a commandName * @param commandName the command name/String for which the Executor is needed. Defaults threadPoolName to commandName * @param requestWrapper requestWrapper having data,hashmap of parameters * @return The executor corresponding to the commandName. * @throws UnsupportedOperationException if doesn't find a TaskHandler in the registry corresponding to the command name */ public Executor<TaskRequestWrapper,TaskResult> getExecutor(String commandName, TaskRequestWrapper requestWrapper) { return this.getExecutor(commandName, commandName, requestWrapper); } private Future<TaskResult> executeAsyncCommand(final long receiveTime, final TaskHandlerExecutor command, final String commandName, final TaskRequestWrapper requestWrapper) { if(command==null) { throw new UnsupportedOperationException("Invoked unsupported command : " + commandName); } else { return command.queue(); } } /** * Executes a command asynchronously. (Returns a future promise) * @param commandName name of the command * @param requestWrapper requestWrapper having data,hashmap of parameters * @param proxyName name of the threadpool in which the command has to be executed * @return thrift result * @throws UnsupportedOperationException if no handler found for command */ public Future<TaskResult> executeAsyncCommand(final String commandName, String proxyName, final TaskRequestWrapper requestWrapper) throws UnsupportedOperationException { final long receiveTime = System.currentTimeMillis(); final TaskHandlerExecutor command = (TaskHandlerExecutor) getExecutor(commandName, proxyName, requestWrapper); return executeAsyncCommand(receiveTime, command, commandName, requestWrapper); } /** * Executes a command * @param commandName name of the command * @param requestWrapper requestWrapper having data,hashmap of parameters * @param proxyName name of the threadpool in which the command has to be executed * @return thrift result * @throws UnsupportedOperationException if no handler found for command */ public <S> TaskResult executeCommand(String commandName, String proxyName, TaskRequestWrapper requestWrapper) throws UnsupportedOperationException { long receiveTime = System.currentTimeMillis(); TaskHandlerExecutor<S> command = (TaskHandlerExecutor) getExecutor(commandName, proxyName, requestWrapper); if(command==null) { throw new UnsupportedOperationException("Invoked unsupported command : " + commandName); } else { try { return command.execute(); } catch (Exception e) { throw new RuntimeException("Error in processing command "+commandName+": " + e.getMessage(), e); } finally { publishEvent(command, receiveTime, requestWrapper); } } } /** * Executes a command * @param commandName name of the command (also the thread pool name) * @param requestWrapper requestWrapper having data,hashmap of parameters * @return thrift result * @throws UnsupportedOperationException if no handler found for command */ public TaskResult executeCommand(String commandName, TaskRequestWrapper requestWrapper) throws UnsupportedOperationException { return this.executeCommand(commandName,commandName, requestWrapper); } /** * Executes a command * @param commandName name of the command (also the thread pool name) * @param requestWrapper requestWrapper having data,hashmap of parameters * @return thrift result * @throws UnsupportedOperationException if no handler found for command */ @SuppressWarnings("unchecked") public <T, S> TaskResult<T> executeCommand(String commandName, TaskRequestWrapper requestWrapper,Decoder<T> decoder) throws UnsupportedOperationException { long receiveTime = System.currentTimeMillis(); TaskHandlerExecutor<S> command = (TaskHandlerExecutor) getExecutor(commandName, commandName, requestWrapper, decoder); if(command==null) { throw new UnsupportedOperationException("Invoked unsupported command : " + commandName); } else { try { return command.execute(); } catch (Exception e) { throw new RuntimeException("Error in processing command "+commandName+": " + e.getMessage(), e); } finally { publishEvent(command, receiveTime, requestWrapper); } } } /** * Executes a command asynchronously. (Returns a future promise) * @param commandName name of the command * @param requestWrapper requestWrapper having data,hashmap of parameters * @return thrift result * @throws UnsupportedOperationException if no handler found for command */ public Future<TaskResult> executeAsyncCommand(String commandName, TaskRequestWrapper requestWrapper, Decoder decoder) throws UnsupportedOperationException { final long receiveTime = System.currentTimeMillis(); TaskHandlerExecutor command = (TaskHandlerExecutor) getExecutor(commandName, commandName, requestWrapper, decoder); return executeAsyncCommand(receiveTime, command, commandName, requestWrapper); } /** * Executes a command asynchronously * @param commandName name of the command (also the thread pool name) * @param requestWrapper requestWrapper having data,hashmap of parameters * @return thrift result * @throws UnsupportedOperationException if no handler found for command */ public Future<TaskResult> executeAsyncCommand(String commandName, TaskRequestWrapper requestWrapper) throws UnsupportedOperationException { return this.executeAsyncCommand(commandName,commandName, requestWrapper); } /** * Helper methods to create and return the appropriate TaskHandlerExecutor instance */ private Executor<TaskRequestWrapper,TaskResult> getTaskHandlerExecutorWithSemaphoreIsolation(TaskRequestWrapper requestWrapper, String refinedCommandName, TaskHandler taskHandler, int maxConcurrentSize) { if (taskHandler instanceof RequestCacheableHystrixTaskHandler){ return new RequestCacheableTaskHandlerExecutor((RequestCacheableHystrixTaskHandler)taskHandler,this.getTaskContext(),refinedCommandName, requestWrapper , maxConcurrentSize); } else { return new TaskHandlerExecutor(taskHandler,this.getTaskContext(),refinedCommandName,requestWrapper , maxConcurrentSize); } } private Executor<TaskRequestWrapper,TaskResult> getTaskHandlerExecutor(TaskRequestWrapper requestWrapper, String refinedCommandName, String refinedProxyName, int minConcurrencySize, int maxConcurrentSize, int executorTimeOut, TaskHandler taskHandler) { if (taskHandler instanceof RequestCacheableHystrixTaskHandler) { return new RequestCacheableTaskHandlerExecutor((RequestCacheableHystrixTaskHandler)taskHandler,this.getTaskContext(), refinedCommandName, executorTimeOut, refinedProxyName, minConcurrencySize, maxConcurrentSize,requestWrapper); } else { return new TaskHandlerExecutor(taskHandler,this.getTaskContext(),refinedCommandName, executorTimeOut, refinedProxyName, minConcurrencySize, maxConcurrentSize,requestWrapper); } } /** * Helper methods to create and return the appropriate TaskHandlerExecutor instance */ private Executor<TaskRequestWrapper,TaskResult> getTaskHandlerExecutorWithSemaphoreIsolationAndDecoder(TaskRequestWrapper requestWrapper, String refinedCommandName, TaskHandler taskHandler, int maxConcurrentSize, Decoder decoder) { if (taskHandler instanceof RequestCacheableHystrixTaskHandler){ return new RequestCacheableTaskHandlerExecutor((RequestCacheableHystrixTaskHandler)taskHandler,this.getTaskContext(),refinedCommandName, requestWrapper , maxConcurrentSize,decoder); } else { return new TaskHandlerExecutor(taskHandler,this.getTaskContext(),refinedCommandName,requestWrapper , maxConcurrentSize, decoder); } } private Executor<TaskRequestWrapper,TaskResult> getTaskHandlerExecutorWithDecoder(TaskRequestWrapper requestWrapper, String refinedCommandName, String refinedProxyName, int minConcurrencySize, int maxConcurrentSize, int executorTimeOut, TaskHandler taskHandler, Decoder decoder) { if (taskHandler instanceof RequestCacheableHystrixTaskHandler) { return new RequestCacheableTaskHandlerExecutor((RequestCacheableHystrixTaskHandler)taskHandler,this.getTaskContext(), refinedCommandName, executorTimeOut, refinedProxyName, minConcurrencySize, maxConcurrentSize,requestWrapper, decoder); } else { return new TaskHandlerExecutor(taskHandler,this.getTaskContext(),refinedCommandName, executorTimeOut, refinedProxyName, minConcurrencySize, maxConcurrentSize,requestWrapper, decoder); } } /** * Helper method to wrap the Executor with request and response interceptors */ private Executor<TaskRequestWrapper,TaskResult> wrapExecutorWithInterceptors(Executor<TaskRequestWrapper,TaskResult> executor, TaskHandler taskHandler) { ClientRequestInterceptor<TaskRequestWrapper> tracingRequestInterceptor = new ClientRequestInterceptor<TaskRequestWrapper>(); CommandClientResponseInterceptor<TaskResult> tracingResponseInterceptor = new CommandClientResponseInterceptor<TaskResult>(); return this.wrapExecutorWithInterceptors(executor, taskHandler, tracingRequestInterceptor, tracingResponseInterceptor); } /** * Helper method to get the execution timeout */ private int getExecutionTimeout(TaskHandler taskHandler, String commandName) { int executionTimeout = HystrixTaskHandler.DEFAULT_EXECUTOR_TIMEOUT; if (taskHandler instanceof HystrixTaskHandler) { executionTimeout = ((HystrixTaskHandler)taskHandler).getExecutorTimeout(commandName); } return executionTimeout; } /** * Helper method to get the max concurrency size for the specified handler */ private int getMaxConcurrency(TaskHandler taskHandler, String proxyName) { int maxConcurrentSize = DEFAULT_THREAD_POOL_SIZE; if(taskHandler instanceof HystrixTaskHandler) { HystrixTaskHandler hystrixTaskHandler = (HystrixTaskHandler) taskHandler; LOGGER.debug("Isolation strategy: "+hystrixTaskHandler.getIsolationStrategy()+" for "+hystrixTaskHandler); if(((TaskHandlerRegistry)getRegistry()).getMaxPoolSize(proxyName)!=null) { LOGGER.debug("Found a predefined max pool size for "+proxyName+". Not using default value of "+DEFAULT_THREAD_POOL_SIZE); maxConcurrentSize = ((TaskHandlerRegistry)getRegistry()).getMaxPoolSize(proxyName); } } return maxConcurrentSize; } private int getMinConcurrency(TaskHandler taskHandler, String proxyName) { int minConcurrentSize = -1; if (taskHandler instanceof HystrixTaskHandler) { HystrixTaskHandler hystrixTaskHandler = (HystrixTaskHandler) taskHandler; LOGGER.debug("Isolation strategy: " + hystrixTaskHandler.getIsolationStrategy() + " for " + hystrixTaskHandler); if (((TaskHandlerRegistry) getRegistry()).getCorePoolSize(proxyName) != null) { LOGGER.debug("Found a predefined core pool size for " + proxyName); minConcurrentSize = ((TaskHandlerRegistry) getRegistry()).getCorePoolSize(proxyName); } } return minConcurrentSize; } /** * Helper method to refine the specified name */ private String getRefinedName(String name) { return name.replaceAll(ONLY_ALPHANUMERIC_REGEX, "").replaceAll(WHITESPACE_REGEX, ""); } /** * Helper method to validate the handler identifications parameters and return a suitable active TaskHandler */ private TaskHandler getTaskHandler(String commandName, String refinedCommandName, String proxyName, String refinedProxyName, RequestWrapper requestWrapper) { if(!commandName.equals(refinedCommandName)) { LOGGER.debug("Command names are not allowed to have Special characters/ whitespaces. Replacing: "+commandName+" with "+refinedCommandName); } if(!proxyName.equals(refinedProxyName)) { LOGGER.debug("Thread pool names are not allowed to have Special characters/ whitespaces. Replacing: "+proxyName+" with "+refinedProxyName); } if(proxyName.isEmpty()) { proxyName=commandName; LOGGER.debug("null/empty threadPoolName passed. defaulting to commandName: "+commandName); } TaskHandler taskHandler = ((TaskHandlerRegistry)getRegistry()).getTaskHandlerByCommand(commandName); if(taskHandler!=null) { if (!taskHandler.isActive()) { LOGGER.error("TaskHandler: "+taskHandler.getName()+" is not yet active. Command: "+commandName+" will not be processed"); return null; } } else { throw new UnsupportedOperationException("Invoked unsupported command : " + commandName); } return taskHandler; } /** * Builds & Publishes Event based using eventProducer * @param command Command under execution * @param receiveTime Time this command has been recieved for execution * @param requestWrapper Request Wrapper to extract request params. */ private void publishEvent(final TaskHandlerExecutor command, final long receiveTime, final TaskRequestWrapper requestWrapper) { if (eventProducer != null) { // Publishes event both in case of success and failure. final Map<String, String> params = requestWrapper.getParams(); ServiceProxyEvent.Builder eventBuilder = command.getEventBuilder().withCommandData(command).withEventSource(command.getClass().getName()); eventBuilder.withRequestId(params.get("requestID")).withRequestReceiveTime(receiveTime); if(params.containsKey("requestSentTime")) { eventBuilder.withRequestSentTime(Long.valueOf(params.get("requestSentTime"))); } eventProducer.publishEvent(eventBuilder.build()); } else { LOGGER.debug("eventProducer not set, not publishing event"); } } /** Getter/Setter methods*/ public void setEventProducer(ServiceProxyEventProducer eventProducer) { this.eventProducer = eventProducer; } }