package com.xiaomi.infra.galaxy.emr.client;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import libthrift091.protocol.TCompactProtocol;
import libthrift091.protocol.TProtocol;
import libthrift091.transport.TTransport;
import org.apache.http.client.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.xiaomi.infra.galaxy.rpc.client.GalaxyHttpClient;
import com.xiaomi.infra.galaxy.rpc.thrift.Credential;
import com.xiaomi.infra.galaxy.rpc.thrift.ThriftProtocol;
import com.xiaomi.infra.galaxy.rpc.util.clock.AdjustableClock;
/**
* Thread safe client proxy.
*
* @author heliangliang
*/
public class ThreadSafeClient<IFace, Impl> {
private static class ThreadSafeInvocationHandler<IFace, Impl> implements InvocationHandler {
private static final Logger LOG = LoggerFactory.getLogger(ThreadSafeInvocationHandler.class);
private final HttpClient client;
private final Map<String, String> customHeaders;
private final Credential credential;
private final AdjustableClock clock;
private static ConcurrentHashMap<Class, Constructor> cotorMap =
new ConcurrentHashMap<Class, Constructor>();
final Class<IFace> ifaceClass;
final Class<Impl> implClass;
final String url;
private int socketTimeout = 0;
private int connTimeout = 0;
private boolean supportAccountKey = false;
private boolean createAsyncClient = false;
private ThreadSafeInvocationHandler(HttpClient client, Map<String, String> customHeaders,
Credential credential, AdjustableClock clock, Class<IFace> ifaceClass,
Class<Impl> implClass, String url, int socketTimeout, int connTimeout,
boolean supportAccountKey) {
this.client = client;
this.customHeaders = customHeaders;
this.credential = credential;
this.clock = clock;
this.ifaceClass = ifaceClass;
this.implClass = implClass;
this.url = url;
this.socketTimeout = socketTimeout;
this.connTimeout = connTimeout;
this.supportAccountKey = supportAccountKey;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
GalaxyHttpClient galaxyHttpClient = new GalaxyHttpClient(url, client, this.credential, clock);
galaxyHttpClient.setSocketTimeout(socketTimeout)
.setConnectTimeout(connTimeout)
.setProtocol(ThriftProtocol.TCOMPACT)
.setQueryString("type=" + method.getName())
.setSupportAccountKey(supportAccountKey);
if (customHeaders != null) {
for (Map.Entry<String, String> header : customHeaders.entrySet()) {
galaxyHttpClient.setCustomHeader(header.getKey(), header.getValue());
}
}
Impl client = createClient(implClass, galaxyHttpClient);
return method.invoke(client, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
private Impl createClient(Class<Impl> implClass, TTransport transport) throws Exception {
Constructor<Impl> cotor = getDeclaredConstructor(implClass);
Impl client = cotor.newInstance(new TCompactProtocol(transport));
return client;
}
private <Impl> Constructor<Impl> getDeclaredConstructor(Class<Impl> implClass) throws NoSuchMethodException {
Constructor<Impl> cotor = cotorMap.get(implClass);
if (cotor == null) {
cotor = implClass.getConstructor(TProtocol.class);
cotorMap.put(implClass, cotor);
}
return cotor;
}
}
/**
* Create client wrapper which automatically retry the RPC calls for retryable errors until
* success or reaches max retry time.
*/
@SuppressWarnings("unchecked")
public static <IFace, Impl> IFace getClient(HttpClient client, Map<String, String> customHeaders,
Credential credential, AdjustableClock clock, Class<IFace> ifaceClass, Class<Impl> implClass,
String url, int socketTimeout, int connTimeout, boolean supportAccountKey) {
return (IFace) Proxy.newProxyInstance(ThreadSafeClient.class.getClassLoader(),
new Class[]{ifaceClass},
new ThreadSafeInvocationHandler<IFace, Impl>(client, customHeaders, credential, clock,
ifaceClass, implClass, url, socketTimeout, connTimeout, supportAccountKey));
}
}