/* * 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.thrift.impl; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.thrift.transport.TTransport; import com.flipkart.phantom.event.ServiceProxyEvent; import com.flipkart.phantom.task.spi.Executor; import com.flipkart.phantom.task.spi.TaskContext; 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.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; /** * <code>ThriftProxyExecutor</code> is an extension of {@link com.netflix.hystrix.HystrixCommand}. It is essentially a * wrapper around {@link ThriftProxy}, providing a means for the ThriftProxy to to be called using * a Hystrix Command. * * @author Regunath B * @version 1.0, 28 March, 2013 */ public class ThriftProxyExecutor extends HystrixCommand<TTransport> implements Executor<ThriftRequestWrapper, TTransport> { /** The default Hystrix group to which the command belongs, unless otherwise mentioned*/ public static final String DEFAULT_HYSTRIX_GROUP = "defaultThriftGroup"; /** The default Hystrix Thread pool to which the command belongs, unless otherwise mentioned */ public static final String DEFAULT_HYSTRIX_THREAD_POOL = "defaultThriftThreadPool"; /** The default Hystrix Thread pool to which this command belongs, unless otherwise mentioned */ public static final int DEFAULT_HYSTRIX_THREAD_POOL_SIZE = 20; /** Event Type for publishing all events which are generated here */ private final static String THRIFT_HANDLER = "THRIFT_HANDLER"; /** The {@link ThriftProxy} or {@link ThriftProxy} instance which this Command wraps around */ protected ThriftProxy thriftProxy; /** The TaskContext instance*/ protected TaskContext taskContext; /** The client's TTransport*/ protected TTransport clientTransport; /** The Thrift request wrapper*/ protected ThriftRequestWrapper thriftRequestWrapper; /** Event which records various paramenters of this request execution & published later */ protected ServiceProxyEvent.Builder eventBuilder; /** List of request and response interceptors */ private List<RequestInterceptor<ThriftRequestWrapper>> requestInterceptors = new LinkedList<RequestInterceptor<ThriftRequestWrapper>>(); private List<ResponseInterceptor<TTransport>> responseInterceptors = new LinkedList<ResponseInterceptor<TTransport>>(); /** * Constructor for this class. * @param hystrixThriftProxy the HystrixThriftProxy that must be wrapped by Hystrix * @param taskContext the TaskContext instance that manages the proxies * @param commandName the Hystrix command name. */ protected ThriftProxyExecutor(HystrixThriftProxy hystrixThriftProxy, TaskContext taskContext, String commandName, ThriftRequestWrapper thriftRequestWrapper) { super(constructHystrixSetter(hystrixThriftProxy,commandName)); this.thriftProxy = hystrixThriftProxy; this.taskContext = taskContext; this.thriftRequestWrapper = thriftRequestWrapper; this.clientTransport = thriftRequestWrapper.getClientSocket(); this.eventBuilder = new ServiceProxyEvent.Builder(commandName, THRIFT_HANDLER); } /** * Interface method implementation. @see HystrixCommand#run() */ @Override protected TTransport run() { this.eventBuilder.withRequestExecutionStartTime(System.currentTimeMillis()); if (this.thriftRequestWrapper.getRequestContext().isPresent() && this.thriftRequestWrapper.getRequestContext().get().getCurrentServerSpan() != null) { Brave.getServerSpanThreadBinder().setCurrentSpan(this.thriftRequestWrapper.getRequestContext().get().getCurrentServerSpan()); } for (RequestInterceptor<ThriftRequestWrapper> requestInterceptor : this.requestInterceptors) { requestInterceptor.process(this.thriftRequestWrapper); } TTransport response = null; Optional<RuntimeException> transportException = Optional.absent(); try { response = thriftProxy.doRequest(this.clientTransport); } catch (RuntimeException e) { transportException = Optional.of(e); throw e; // rethrow this for it to handled by other layers in the call stack } finally { // close the response if the command timed out if (this.isResponseTimedOut()) { if( response!= null ) { response.close(); } } for (ResponseInterceptor<TTransport> responseInterceptor : this.responseInterceptors) { responseInterceptor.process(response, transportException); } } return response; } /** * Interface method implementation. @see HystrixCommand#getFallback() */ @Override protected TTransport getFallback() { Map<String, Object> controlparams = new HashMap<String,Object>(); // check and populate execution error root cause, if any, for use in fallback if (this.isFailedExecution()) { controlparams.put(Executor.EXECUTION_ERROR_CAUSE, this.getFailedExecutionException()); } if(this.thriftProxy instanceof HystrixThriftProxy) { HystrixThriftProxy hystrixThriftProxy = (HystrixThriftProxy) this.thriftProxy; hystrixThriftProxy.fallbackThriftRequest(this.clientTransport,controlparams); return this.clientTransport; } return null; } /** * Helper method that constructs the {@link com.netflix.hystrix.HystrixCommand.Setter} according to the properties set in the {@link ThriftProxy}. * Falls back to default properties, if any of them has not been set * * @param hystrixThriftProxy The {@link ThriftProxy} for whom properties have to be set * @return Setter */ private static Setter constructHystrixSetter(HystrixThriftProxy hystrixThriftProxy, String commandName) { Setter setter = null; if((hystrixThriftProxy.getGroupName()!=null) || !hystrixThriftProxy.getGroupName().equals("")) { setter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(hystrixThriftProxy.getGroupName())); } else { setter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(hystrixThriftProxy.getName())); } setter = setter.andCommandKey(HystrixCommandKey.Factory.asKey(commandName)); if((hystrixThriftProxy.getThreadPoolName()!=null) || !hystrixThriftProxy.getThreadPoolName().equals("")) { setter = setter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(hystrixThriftProxy.getThreadPoolName())); } else { setter = setter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(DEFAULT_HYSTRIX_THREAD_POOL)); } Integer threadPoolSize = hystrixThriftProxy.getProxyThreadPoolSize() == null ? ThriftProxyExecutor.DEFAULT_HYSTRIX_THREAD_POOL_SIZE : hystrixThriftProxy.getProxyThreadPoolSize(); setter = setter.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(threadPoolSize)); setter = setter.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationThreadTimeoutInMilliseconds(hystrixThriftProxy.getExecutorTimeout(commandName))); if((hystrixThriftProxy.getHystrixProperties()!=null)) { setter = setter.andCommandPropertiesDefaults(hystrixThriftProxy.getHystrixProperties()); } return setter; } /** * 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<ThriftRequestWrapper> 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<TTransport> responseInterceptor){ this.responseInterceptors.add(responseInterceptor); } /** * Interface method implementation. Returns the name of the ThriftProxy used by this Executor * @see com.flipkart.phantom.task.spi.Executor#getServiceName() */ public Optional<String> getServiceName() { return Optional.of(this.thriftProxy.getName()); } /** * Interface method implementation. Returns the ThriftRequestWrapper instance that this Executor was created with * @see com.flipkart.phantom.task.spi.Executor#getRequestWrapper() */ public ThriftRequestWrapper getRequestWrapper() { return this.thriftRequestWrapper; } /**Getter/Setter methods */ public TTransport getClientTransport() { return this.clientTransport; } public void setClientTransport(TTransport clientTransport) { this.clientTransport = clientTransport; } public ServiceProxyEvent.Builder getEventBuilder() { return eventBuilder; } /** End Getter/Setter methods */ }