![JAR search and dependency download from the Maven repository](/logo.png)
com.github.kubatatami.judonetworking.RequestConnector Maven / Gradle / Ivy
package com.github.kubatatami.judonetworking;
import android.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class RequestConnector {
private final String url;
private final EndpointImplementation rpc;
private final Connector connector;
private final Random randomGenerator = new Random();
public RequestConnector(String url, EndpointImplementation rpc, Connector connector) {
this.url = url;
this.rpc = rpc;
this.connector = connector;
}
private static void longLog(String tag, String message) {
LoggerImpl.longLog(tag, message);
}
private static String convertStreamToString(InputStream is) {
Scanner s = new Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
private RequestResult sendRequest(Request request, TimeStat timeStat) {
return sendRequest(request, timeStat, null, null);
}
private RequestResult sendRequest(Request request, TimeStat timeStat, String hash, Long time) {
try {
RequestResult result;
Object virtualObject = handleVirtualServerRequest(request, timeStat);
if (virtualObject != null) {
result = new RequestSuccessResult(request.getId(), virtualObject);
} else {
ProtocolController controller = rpc.getProtocolController();
ProtocolController.RequestInfo requestInfo = controller.createRequest(url, request);
timeStat.tickCreateTime();
lossCheck();
delay();
Connector.Connection conn = connector.send(controller, requestInfo, request.getTimeout(), timeStat,
rpc.getDebugFlags(), request.getMethod(), new Connector.CacheInfo(hash, time));
if (!conn.isNewestAvailable()) {
if ((rpc.getDebugFlags() & Endpoint.RESPONSE_DEBUG) > 0) {
LoggerImpl.log("No new data for method " + request.getName());
}
return new NoNewResult();
}
InputStream connectionStream = conn.getStream();
if ((rpc.getDebugFlags() & Endpoint.RESPONSE_DEBUG) > 0) {
String resStr = convertStreamToString(conn.getStream());
longLog("Response(" + resStr.length() + "B)", resStr);
connectionStream = new ByteArrayInputStream(resStr.getBytes("UTF-8"));
}
RequestInputStream stream = new RequestInputStream(connectionStream, timeStat, conn.getContentLength());
result = controller.parseResponse(request, stream, conn.getHeaders());
if (result instanceof RequestSuccessResult) {
result.hash = conn.getHash();
result.time = conn.getDate();
}
timeStat.tickParseTime();
conn.close();
}
if (result instanceof RequestSuccessResult) {
if (rpc.isVerifyResultModel()) {
verifyResult(request, result);
}
if (rpc.isProcessingMethod()) {
processingMethod(result.result);
}
}
return result;
} catch (Exception e) {
return new ErrorResult(request.getId(), e);
}
}
public static void processingMethod(Object object) {
if (object instanceof Iterable) {
for (Object obj : ((Iterable) object)) {
processingMethod(obj);
}
} else {
for (Field field : object.getClass().getFields()) {
field.setAccessible(true);
try {
if (!field.getDeclaringClass().equals(Object.class) && !field.getType().isPrimitive()) {
Object fieldObject = field.get(object);
if (fieldObject != null) {
processingMethod(fieldObject);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
invokeProcessingMethod(object);
}
}
public static void invokeProcessingMethod(Object result) {
Class> clazz = result.getClass();
do {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(ProcessingMethod.class)) {
method.setAccessible(true);
try {
method.invoke(result);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
clazz = clazz.getSuperclass();
} while (!clazz.equals(Object.class));
}
public static void verifyResult(Request request, RequestResult result) throws RequestException {
if (result instanceof RequestSuccessResult && !request.getReturnType().equals(Void.class)) {
if (result.result == null) {
Required ann = request.getMethod().getAnnotation(Required.class);
if (ann != null) {
throw new RequestException("Result object required.");
}
RequiredList ann2 = request.getMethod().getAnnotation(RequiredList.class);
if (ann2 != null) {
throw new RequestException("Result object required.");
}
} else {
RequiredList ann = request.getMethod().getAnnotation(RequiredList.class);
if (ann != null) {
if (result.result instanceof Iterable) {
int i = 0;
for (Object obj : (Iterable) result.result) {
verifyResultObject(obj);
i++;
}
if (ann.minSize() > 0 && i < ann.minSize()) {
throw new RequestException("Result list from method " + request.getName() + "(size " + i + ") is smaller then limit: " + ann.minSize() + ".");
}
if (ann.maxSize() > 0 && i > ann.maxSize()) {
throw new RequestException("Result list from method " + request.getName() + "(size " + i + ") is larger then limit: " + ann.maxSize() + ".");
}
}
}
verifyResultObject(result.result);
}
}
}
public static void verifyResultObject(Object object) throws RequestException {
if (object instanceof Iterable) {
for (Object obj : ((Iterable) object)) {
verifyResultObject(obj);
}
} else {
for (Field field : object.getClass().getFields()) {
try {
field.setAccessible(true);
if (field.get(object) == null) {
if (field.isAnnotationPresent(Required.class) || field.isAnnotationPresent(RequiredList.class)) {
throw new RequestException("Field " + object.getClass().getName() + "." + field.getName() + " required.");
}
} else {
Object iterableObject = field.get(object);
if (iterableObject instanceof Iterable) {
RequiredList ann = field.getAnnotation(RequiredList.class);
if (ann != null) {
int i = 0;
for (Object obj : (Iterable) iterableObject) {
verifyResultObject(obj);
i++;
}
if (ann.minSize() > 0 && i < ann.minSize()) {
throw new RequestException("List " + object.getClass().getName() + "." + field.getName() + "(size " + i + ") is smaller then limit: " + ann.minSize() + ".");
}
if (ann.maxSize() > 0 && i > ann.maxSize()) {
throw new RequestException("List " + object.getClass().getName() + "." + field.getName() + "(size " + i + ") is larger then limit: " + ann.maxSize() + ".");
}
}
} else if (field.getAnnotation(Required.class) != null) {
verifyResultObject(field.get(object));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
public static Object[] addElement(Object[] org, Object added) {
Object[] result = new Object[org.length + 1];
System.arraycopy(org, 0, result, 0, org.length);
result[org.length] = added;
return result;
}
private Object handleVirtualServerRequest(Request request, TimeStat timeStat) throws Exception {
VirtualServerInfo virtualServerInfo = rpc.getVirtualServers().get(request.getMethod().getDeclaringClass());
if (virtualServerInfo != null) {
if (request.getCallback() == null) {
try {
Object object = request.getMethod().invoke(virtualServerInfo.server, request.getArgs());
int delay = randDelay(virtualServerInfo.minDelay, virtualServerInfo.maxDelay);
for (int i = 0; i <= TimeStat.TICKS; i++) {
Thread.sleep(delay / TimeStat.TICKS);
timeStat.tickTime(i);
}
return object;
} catch (InvocationTargetException ex) {
if (ex.getCause() == null || !(ex.getCause() instanceof UnsupportedOperationException)) {
throw ex;
}
}
} else {
VirtualCallback callback = new VirtualCallback(request.getId());
Object[] args = request.getArgs() != null ? addElement(request.getArgs(), callback) : new Object[]{callback};
boolean implemented = true;
try {
request.getMethod().invoke(virtualServerInfo.server, args);
int delay = randDelay(virtualServerInfo.minDelay, virtualServerInfo.maxDelay);
for (int i = 0; i <= TimeStat.TICKS; i++) {
Thread.sleep(delay / TimeStat.TICKS);
timeStat.tickTime(i);
}
} catch (InvocationTargetException ex) {
if (ex.getCause() != null && ex.getCause() instanceof UnsupportedOperationException) {
implemented = false;
} else {
throw ex;
}
}
if (implemented) {
if (callback.getResult().error != null) {
throw callback.getResult().error;
}
return callback.getResult().result;
}
}
}
return null;
}
protected Base64Param findBase64Annotation(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation instanceof Base64Param) {
return (Base64Param) annotation;
}
}
return null;
}
protected void findAndCreateBase64(Request request) {
if (request.getArgs() != null) {
int i = 0;
Annotation[][] annotations = request.getMethod().getParameterAnnotations();
for (Object object : request.getArgs()) {
if (object instanceof byte[]) {
Base64Param ann = findBase64Annotation(annotations[i]);
if (ann != null) {
request.getArgs()[i] = ann.prefix() + Base64.encodeToString((byte[]) object, ann.type()) + ann.suffix();
}
}
i++;
}
}
}
@SuppressWarnings("unchecked")
public Object call(Request request) throws Exception {
try {
CacheResult localCacheObject = null;
CacheResult serverCacheObject = null;
TimeStat timeStat = new TimeStat(request);
if ((rpc.isCacheEnabled() && request.isLocalCachable()) || rpc.isTest()) {
LocalCacheLevel cacheLevel = rpc.isTest() ? LocalCacheLevel.DISK_CACHE : request.getLocalCacheLevel();
localCacheObject = rpc.getMemoryCache().get(request.getMethod(), request.getArgs(), rpc.isTest() ? 0 : request.getLocalCacheLifeTime(), request.getLocalCacheSize());
if (localCacheObject.result) {
if (!request.isLocalCacheOnlyOnError()) {
timeStat.tickCacheTime();
return localCacheObject.object;
}
} else if (cacheLevel != LocalCacheLevel.MEMORY_ONLY) {
CacheMethod cacheMethod = new CacheMethod(rpc.getTestName(), rpc.getTestRevision(), url, request.getMethod(), cacheLevel);
localCacheObject = rpc.getDiskCache().get(cacheMethod, Arrays.deepToString(request.getArgs()), request.getLocalCacheLifeTime());
if (localCacheObject.result) {
if (!rpc.isTest()) { //we don't know when test will be stop
rpc.getMemoryCache().put(request.getMethod(), request.getArgs(), localCacheObject.object, request.getLocalCacheSize());
}
if (!request.isLocalCacheOnlyOnError()) {
timeStat.tickCacheTime();
return localCacheObject.object;
}
}
}
}
if (rpc.isCacheEnabled() && request.isServerCachable()) {
CacheMethod cacheMethod = new CacheMethod(url, request.getMethod(), request.getServerCacheLevel());
serverCacheObject = rpc.getDiskCache().get(cacheMethod, Arrays.deepToString(request.getArgs()), 0);
}
findAndCreateBase64(request);
RequestResult result;
if (serverCacheObject != null && serverCacheObject.result) {
result = sendRequest(request, timeStat, serverCacheObject.hash, serverCacheObject.time);
if (result instanceof NoNewResult) {
return serverCacheObject.object;
} else if (result instanceof ErrorResult && request.useServerCacheOldOnError()) {
return serverCacheObject.object;
}
} else {
result = sendRequest(request, timeStat, null, null);
}
if (result instanceof ErrorResult) {
if (request.isLocalCacheOnlyOnError() && localCacheObject != null && localCacheObject.result) {
timeStat.tickCacheTime();
return localCacheObject.object;
}
}
if (result.error != null) {
throw result.error;
}
timeStat.tickEndTime();
if (rpc.isTimeProfiler()) {
refreshStat(request.getName(), timeStat.getMethodTime());
}
if ((rpc.getDebugFlags() & Endpoint.TIME_DEBUG) > 0) {
timeStat.logTime("End single request(" + request.getName() + "):");
}
if ((rpc.isCacheEnabled() && request.isLocalCachable()) || rpc.isTest()) {
rpc.getMemoryCache().put(request.getMethod(), request.getArgs(), result.result, request.getLocalCacheSize());
if (rpc.getCacheMode() == CacheMode.CLONE) {
result.result = rpc.getClonner().clone(result.result);
}
LocalCacheLevel cacheLevel = rpc.isTest() ? LocalCacheLevel.DISK_CACHE : request.getLocalCacheLevel();
if (cacheLevel != LocalCacheLevel.MEMORY_ONLY) {
CacheMethod cacheMethod = new CacheMethod(rpc.getTestName(), rpc.getTestRevision(), url, request.getMethod(), cacheLevel);
rpc.getDiskCache().put(cacheMethod, Arrays.deepToString(request.getArgs()), result.result, request.getLocalCacheSize());
}
} else if (rpc.isCacheEnabled() && request.isServerCachable() && (result.hash != null || result.time != null)) {
CacheMethod cacheMethod = new CacheMethod(url, request.getMethod(), request.getServerCacheLevel());
rpc.getDiskCache().put(cacheMethod, Arrays.deepToString(request.getArgs()), result.result, request.getServerCacheSize());
}
return result.result;
} catch (Exception e) {
refreshErrorStat(request.getName(), request.getTimeout());
throw e;
}
}
public List callBatch(List requests, ProgressObserver progressObserver, Integer timeout) throws Exception {
final List results = new ArrayList(requests.size());
if (requests.size() > 0) {
if (rpc.getProtocolController().isBatchSupported()) {
List copyRequest = new ArrayList(requests);
VirtualServerInfo virtualServerInfo = rpc.getVirtualServers().get(requests.get(0).getMethod().getDeclaringClass());
if (virtualServerInfo != null) {
TimeStat timeStat = new TimeStat(progressObserver);
int delay = randDelay(virtualServerInfo.minDelay, virtualServerInfo.maxDelay);
for (int i = copyRequest.size() - 1; i >= 0; i--) {
Request request = copyRequest.get(i);
VirtualCallback callback = new VirtualCallback(request.getId());
Object[] args = request.getArgs() != null ? addElement(request.getArgs(), callback) : new Object[]{callback};
boolean implemented = true;
try {
request.getMethod().invoke(virtualServerInfo.server, args);
} catch (InvocationTargetException ex) {
if (ex.getCause() != null && ex.getCause() instanceof UnsupportedOperationException) {
implemented = false;
} else {
throw ex;
}
}
if (implemented) {
results.add(callback.getResult());
copyRequest.remove(request);
}
}
if (copyRequest.size() == 0) {
for (int z = 0; z < TimeStat.TICKS; z++) {
Thread.sleep(delay / TimeStat.TICKS);
timeStat.tickTime(z);
}
}
}
String requestsName = "";
for (Request request : requests) {
requestsName += " " + request.getName();
findAndCreateBase64(request);
}
if (copyRequest.size() > 0) {
results.addAll(callRealBatch(copyRequest, progressObserver, timeout, requestsName));
}
} else {
for (Request request : requests) {
findAndCreateBase64(request);
}
synchronized (progressObserver) {
progressObserver.setMaxProgress(progressObserver.getMaxProgress() + (requests.size() - 1) * TimeStat.TICKS);
}
ExecutorService executors = Executors.newFixedThreadPool(rpc.getMaxConnections());
List> todo = new ArrayList>();
for (final Request request : requests) {
final TimeStat timeStat = new TimeStat(progressObserver);
todo.add(Executors.callable(new Runnable() {
@Override
public void run() {
CacheResult cacheObject = null;
if (rpc.isCacheEnabled() && request.isServerCachable()) {
CacheMethod cacheMethod = new CacheMethod(url, request.getMethod(), request.getServerCacheLevel());
cacheObject = rpc.getDiskCache().get(cacheMethod, Arrays.deepToString(request.getArgs()), request.getServerCacheSize());
}
if (cacheObject != null && cacheObject.result) {
RequestResult result = sendRequest(request, timeStat, cacheObject.hash, cacheObject.time);
synchronized (results) {
if (result instanceof NoNewResult) {
results.add(new RequestSuccessResult(request.getId(), cacheObject.object));
} else if (result instanceof ErrorResult && request.useServerCacheOldOnError()) {
results.add(new RequestSuccessResult(request.getId(), cacheObject.object));
} else {
results.add(result);
}
}
} else {
synchronized (results) {
results.add(sendRequest(request, timeStat));
}
}
}
}));
}
executors.invokeAll(todo);
executors.shutdown();
}
}
return results;
}
private void delay() {
int delay = rpc.getDelay();
if (delay > 0) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public List callRealBatch(List requests, ProgressObserver progressObserver, Integer timeout, String requestsName) throws Exception {
try {
ProtocolController controller = rpc.getProtocolController();
List responses;
TimeStat timeStat = new TimeStat(progressObserver);
ProtocolController.RequestInfo requestInfo = controller.createRequest(url, (List) requests);
timeStat.tickCreateTime();
lossCheck();
delay();
Connector.Connection conn = connector.send(controller, requestInfo, timeout, timeStat, rpc.getDebugFlags(), null, null);
InputStream connectionStream = conn.getStream();
if ((rpc.getDebugFlags() & Endpoint.RESPONSE_DEBUG) > 0) {
String resStr = convertStreamToString(conn.getStream());
longLog("Response body(" + resStr.length() + " Bytes)", resStr);
connectionStream = new ByteArrayInputStream(resStr.getBytes("UTF-8"));
}
RequestInputStream stream = new RequestInputStream(connectionStream, timeStat, conn.getContentLength());
responses = controller.parseResponses((List) requests, stream, conn.getHeaders());
timeStat.tickParseTime();
conn.close();
timeStat.tickEndTime();
if (rpc.isTimeProfiler()) {
for (Request request : requests) {
refreshStat(request.getName(), timeStat.getMethodTime() / requests.size());
}
}
if ((rpc.getDebugFlags() & Endpoint.TIME_DEBUG) > 0) {
timeStat.logTime("End batch request(" + requestsName.substring(1) + "):");
}
return responses;
} catch (Exception e) {
for (Request request : requests) {
refreshErrorStat(request.getName(), request.getTimeout());
}
throw new RequestException(requestsName.substring(1), e);
}
}
private void lossCheck() throws RequestException {
float precentLoss = rpc.getPercentLoss();
float random = randomGenerator.nextFloat();
if (precentLoss != 0 && random < precentLoss) {
throw new RequestException("Random package lost.");
}
}
private MethodStat getStat(String method) {
MethodStat stat;
if (rpc.getStats().containsKey(method)) {
stat = rpc.getStats().get(method);
} else {
stat = new MethodStat();
rpc.getStats().put(method, stat);
}
return stat;
}
private void refreshStat(String method, long time) {
MethodStat stat = getStat(method);
stat.avgTime = ((stat.avgTime * stat.requestCount) + time) / (stat.requestCount + 1);
stat.requestCount++;
rpc.saveStat();
}
private void refreshErrorStat(String method, long timeout) {
MethodStat stat = getStat(method);
stat.avgTime = ((stat.avgTime * stat.requestCount) + timeout) / (stat.requestCount + 1);
stat.errors++;
stat.requestCount++;
rpc.saveStat();
}
public void setReconnections(int reconnections) {
connector.setReconnections(reconnections);
}
public void setConnectTimeout(int connectTimeout) {
connector.setConnectTimeout(connectTimeout);
}
public void setMethodTimeout(int methodTimeout) {
connector.setMethodTimeout(methodTimeout);
}
public int getMethodTimeout() {
return connector.getMethodTimeout();
}
public int randDelay(int minDelay, int maxDelay) {
if (maxDelay == 0) {
return 0;
}
Random random = new Random();
if (maxDelay != minDelay) {
return minDelay + random.nextInt(maxDelay - minDelay);
} else {
return minDelay;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy