Java tutorial
/* * 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 com.flipkart.phantom.task.spi.AbstractHandler; import com.flipkart.phantom.task.spi.TaskContext; import com.flipkart.phantom.thrift.impl.proxy.SocketObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.apache.thrift.ProcessFunction; import org.apache.thrift.TBase; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TMessage; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.protocol.TProtocolFactory; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import java.lang.reflect.Method; import java.net.Socket; import java.util.HashMap; import java.util.Map; /** * <code>ThriftProxy</code> holds the details of a ThriftProxy and loads the necessary Thrift Classes. * Note that this class works only with Thrift classes generated using the IDL compiler version 0.9. This is because * it uses reflection to determine declared methods on the interface. The target service may be of any version. * This implementation has been tested with Thrift versions 0.6 and 0.2. * * @author Regunath B * @version 1.0, 28 March, 2013 */ public abstract class ThriftProxy extends AbstractHandler implements InitializingBean { /** Thrift transport errors */ private static final Map<Integer, String> THRIFT_ERRORS = new HashMap<Integer, String>(); static { THRIFT_ERRORS.put(TTransportException.UNKNOWN, "Unknown exception"); THRIFT_ERRORS.put(TTransportException.NOT_OPEN, "Transport not open"); THRIFT_ERRORS.put(TTransportException.ALREADY_OPEN, "Transport already open"); THRIFT_ERRORS.put(TTransportException.TIMED_OUT, "Thrift timed out"); THRIFT_ERRORS.put(TTransportException.END_OF_FILE, "Reached end of file"); } /** The default Thrift interface class name for Thrift services */ private static final String DEFAULT_SERVICE_INTERFACE_NAME = "Iface"; /** The default Thrift TProcessor class name */ private static final String DEFAULT_PROCESSOR_CLASS_NAME = "Processor"; /** The default Thrift call result class name */ private static final String DEFAULT_RESULT_CLASS_NAME = "_result"; /** Logger for this class*/ private static final Logger LOGGER = LoggerFactory.getLogger(ThriftProxy.class); /** The Thrift binary protocol factory*/ private TProtocolFactory protocolFactory = new TBinaryProtocol.Factory(); /** The target Thrift server connect details*/ private String thriftServer; private int thriftPort; private int thriftTimeoutMillis = -1; /** The fully qualified class name of the Thrift service generated by the Thrift compiler from the IDL file*/ private String thriftServiceClass; /** Map of the method names and the respective Thrift ProcessFunction instances*/ @SuppressWarnings("rawtypes") protected Map<String, ProcessFunction> processMap = new HashMap<String, ProcessFunction>(); /** Properties for initializing Generic Object Pool */ private int poolSize = 10; private long maxWait = 100; private int maxIdle = poolSize; private int minIdle = poolSize / 2; private long timeBetweenEvictionRunsMillis = 20000; /** The GenericObjectPool object */ private GenericObjectPool<Socket> socketPool; /** * Interface method implementation. Checks if all mandatory properties have been set * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() throws Exception { Assert.notNull(this.thriftServer, "The 'thriftServer' may not be null"); Assert.notNull(this.thriftServiceClass, "The 'thriftServiceClass' may not be null"); } /** * Initialize this ThriftProxy */ public void init(TaskContext context) throws Exception { if (this.thriftServiceClass == null) { throw new AssertionError("The 'thriftServiceClass' may not be null"); } if (this.processMap == null || this.processMap.isEmpty()) { throw new AssertionError( "ProcessFunctions not populated. Maybe The 'thriftServiceClass' is not a valid class?"); } if (this.thriftTimeoutMillis == -1) { // implying none set throw new Exception("'thriftTimeoutMillis' must be set to a non-negative value!"); } //Create pool this.socketPool = new GenericObjectPool<Socket>(new SocketObjectFactory(this), this.poolSize, GenericObjectPool.WHEN_EXHAUSTED_GROW, this.maxWait, this.maxIdle, this.minIdle, false, false, this.timeBetweenEvictionRunsMillis, GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN, GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS, true); } /** * * Is called by the {@link com.flipkart.phantom.thrift.impl.ThriftProxyExecutor#run()} for processing the request. * Currently not utilizing the generic pooled objects. * * @param clientTransport * @return transport {@link TTransport} containing clientOutput * @throws Exception */ @SuppressWarnings("rawtypes") public TTransport doRequest(TTransport clientTransport) { TSocket serviceSocket = null; try { //Get Protocol from transport TProtocol clientProtocol = this.protocolFactory.getProtocol(clientTransport); TMessage message = clientProtocol.readMessageBegin(); //Arguments ProcessFunction invokedProcessFunction = this.getProcessMap().get(message.name); if (invokedProcessFunction == null) { throw new RuntimeException( "Unable to find a matching ProcessFunction for invoked method : " + message.name); } TBase args = invokedProcessFunction.getEmptyArgsInstance(); // get the empty args. The values will then be read from the client's TProtocol //Read the argument values from the client's TProtocol args.read(clientProtocol); clientProtocol.readMessageEnd(); // Instantiate the call result object using the Thrift naming convention used for classes TBase result = (TBase) Class .forName(this.getThriftServiceClass() + "$" + message.name + DEFAULT_RESULT_CLASS_NAME) .newInstance(); serviceSocket = new TSocket(this.getThriftServer(), this.getThriftPort(), this.getThriftTimeoutMillis()); TProtocol serviceProtocol = new TBinaryProtocol(serviceSocket); serviceSocket.open(); //Send the arguments to the server and relay the response back //Create the custom TServiceClient client which sends request to actual Thrift servers and relays the response back to the client ProxyServiceClient proxyClient = new ProxyServiceClient(clientProtocol, serviceProtocol, serviceProtocol); //Send the request proxyClient.sendBase(message.name, args, message.seqid); //Get the response back (it is written to client's TProtocol) proxyClient.receiveBase(result, message.name); LOGGER.debug("Processed message : " + this.getThriftServiceClass() + "." + message.name); } catch (Exception e) { if (e.getClass().isAssignableFrom(TTransportException.class)) { //isConnectionValid = false; throw new RuntimeException("Thrift transport exception executing the proxy service call : " + THRIFT_ERRORS.get(((TTransportException) e).getType()), e); } else { throw new RuntimeException("Exception executing the proxy service call : " + e.getMessage(), e); } } finally { if (serviceSocket != null) { serviceSocket.close(); } } return clientTransport; } /** * Gets a pooled TSocket instance * @return a TSocket instance */ public TSocket getPooledSocket() { try { return new TSocket(this.socketPool.borrowObject()); } catch (Exception e) { LOGGER.error("Error while borrowing TSocket : " + e.getMessage(), e); throw new RuntimeException("Error while borrowing TSocket : " + e.getMessage(), e); } } /** * Returns the specified TSocket back to the pool * @param socket the pooled TSocket instance * @param isConnectionValid flag to indicate if the socket was found to be invalid during use */ public void returnPooledSocket(TSocket socket, boolean isConnectionValid) { try { if (isConnectionValid) { this.socketPool.returnObject(socket.getSocket()); } else { this.socketPool.invalidateObject(socket.getSocket()); } } catch (Exception e) { LOGGER.error("Error while returning TSocket : " + e.getMessage(), e); throw new RuntimeException("Error while borrowing TSocket : " + e.getMessage(), e); } } /** * Get the name of this ThriftProxy. * @return the name of this ThriftProxy */ public String getName() { return this.thriftServiceClass; } /** * Abstract method implementation * @see com.flipkart.phantom.task.spi.AbstractHandler#getType() */ public String getType() { return "ThriftProxy"; } /** * Shutdown hooks provided by the ThriftProxy */ public void shutdown(TaskContext context) throws Exception { super.deactivate(); } /** Getter/Setter methods */ public String getThriftServer() { return thriftServer; } public void setThriftServer(String thriftServer) { this.thriftServer = thriftServer; } public int getThriftPort() { return thriftPort; } public void setThriftPort(int thriftPort) { this.thriftPort = thriftPort; } public int getThriftTimeoutMillis() { return thriftTimeoutMillis; } public void setThriftTimeoutMillis(int thriftTimeoutMillis) { this.thriftTimeoutMillis = thriftTimeoutMillis; } @SuppressWarnings("rawtypes") public void setThriftServiceClass(String thriftServiceClass) { this.thriftServiceClass = thriftServiceClass; // Inspect and add ProcessFunction instances for all public methods on the declared service interface String serviceInterfaceClass = this.thriftServiceClass + "$" + DEFAULT_SERVICE_INTERFACE_NAME; try { Class serviceClass = Class.forName(serviceInterfaceClass); Method[] methods = serviceClass.getDeclaredMethods(); for (Method method : methods) { String processFunctionClass = this.thriftServiceClass + "$" + DEFAULT_PROCESSOR_CLASS_NAME + "$" + method.getName(); this.processMap.put(method.getName(), (ProcessFunction) Class.forName(processFunctionClass).newInstance()); } } catch (Exception e) { LOGGER.error("Unable to inspect specified Thrift service class. Error is : " + e.getMessage(), e); // empty the processMap. This will fail the init of this handler in #afterPropertiesSet() this.processMap.clear(); } } public String getThriftServiceClass() { return thriftServiceClass; } public Map<String, ProcessFunction> getProcessMap() { return processMap; } public int getPoolSize() { return poolSize; } public void setPoolSize(int poolSize) { this.poolSize = poolSize; } public long getMaxWait() { return maxWait; } public void setMaxWait(long maxWait) { this.maxWait = maxWait; } public int getMaxIdle() { return maxIdle; } public void setMaxIdle(int maxIdle) { this.maxIdle = maxIdle; } public int getMinIdle() { return minIdle; } public void setMinIdle(int minIdle) { this.minIdle = minIdle; } public long getTimeBetweenEvictionRunsMillis() { return timeBetweenEvictionRunsMillis; } public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; } /** End Getter/Setter methods */ }