MSPF-16: Api client part

This commit is contained in:
Vladimir Pankrashkin 2016-04-27 19:03:48 +03:00
parent 83ed98930f
commit d3bb9f1f73
47 changed files with 1549 additions and 0 deletions

18
pom.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<groupId>com.rbkmoney.woody</groupId>
<artifactId>woody</artifactId>
<version>1.0.0</version>
<description>Java implementation for Woody spec</description>
<modules>
<module>woody-api</module>
<module>woody-thrift</module>
</modules>
</project>

37
woody-api/pom.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>woody</artifactId>
<groupId>com.rbkmoney.woody</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>woody-api</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,147 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.provider.ClientProviderControl;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.proxy.ProxyFactory;
import com.rbkmoney.woody.api.trace.context.ContextTracer;
import com.rbkmoney.woody.api.trace.context.EventListenerTracer;
import com.rbkmoney.woody.api.trace.context.MetadataTracer;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import java.net.URI;
/**
* Created by vpankrashkin on 25.04.16.
*/
public abstract class AbstractClientBuilder implements ClientBuilder {
private URI address;
private ClientEventListener eventListener;
private IdGenerator idGenerator;
@Override
public ClientBuilder withAddress(URI address) {
this.address = address;
return this;
}
@Override
public ClientBuilder withEventListener(ClientEventListener listener) {
this.eventListener = listener;
return this;
}
@Override
public ClientBuilder withIdGenerator(IdGenerator generator) {
this.idGenerator = generator;
return this;
}
@Override
public <T> T build(Class<T> clientInterface) {
return createProxyClient(clientInterface, null);
}
@Override
public <T> T build(Class<T> clientInterface, ClientProviderControl providerControl) {
T target = null;
return createProxyClient(clientInterface, target);
}
protected <T> T createProxyClient(Class<T> clientInterface, T target) {
ProxyBuilder proxyBuilder = new ProxyBuilder();
proxyBuilder.setIdGenerator(idGenerator);
proxyBuilder.setStartEventListener(getEventStartListener(eventListener));
proxyBuilder.setEndEventListener(getEventEndListener(eventListener));
proxyBuilder.setErrEventListener(getErrorListener(eventListener));
proxyBuilder.setStartEventPhases(ProxyBuilder.BEFORE_CALL_START);
proxyBuilder.setEndEventPhases(ProxyBuilder.BEFORE_CONTEXT_DESTROY);
return proxyBuilder.build(clientInterface, target);
}
abstract protected Runnable getErrorListener(ClientEventListener eventListener);
abstract protected Runnable getEventStartListener(ClientEventListener eventListener);
abstract protected Runnable getEventEndListener(ClientEventListener eventListener);
abstract <T> T createProxyTarget(Class<T> clientInterface, ClientEventListener listener, ClientProviderControl providerControl);
protected static class ProxyBuilder {
private static final int AFTER_CONTEXT_INIT = 0b01;
private static final int BEFORE_CONTEXT_DESTROY = 0b10;
private static final int BEFORE_CALL_START = 0b100;
private static final int AFTER_CALL_END = 0b1000;
private int startEventPhases;
private int endEventPhases;
private boolean allowObjectOverriding = false;
private final Runnable stub = () -> {
};
private Runnable startEventListener;
private Runnable endEventListener;
private Runnable errEventListener;
private IdGenerator idGenerator;
public void setStartEventListener(Runnable startEventListener) {
this.startEventListener = startEventListener;
}
public void setEndEventListener(Runnable endEventListener) {
this.endEventListener = endEventListener;
}
public void setErrEventListener(Runnable errEventListener) {
this.errEventListener = errEventListener;
}
public void setIdGenerator(IdGenerator idGenerator) {
this.idGenerator = idGenerator;
}
public void setAllowObjectOverriding(boolean allowObjectOverriding) {
this.allowObjectOverriding = allowObjectOverriding;
}
public void setStartEventPhases(int phases) {
startEventPhases = phases;
}
public void setEndEventPhases(int phases) {
endEventPhases = phases;
}
public ProxyFactory createProxyFactory() {
return new ProxyFactory(createMethodCallTracer(), allowObjectOverriding);
}
public MethodCallTracer createMethodCallTracer() {
return new ContextTracer(createTraceContext(), createEventTracer());
}
public TraceContext createTraceContext() {
return TraceContext.forClient(idGenerator,
hasFlag(AFTER_CONTEXT_INIT, startEventPhases) ? startEventListener : stub,
hasFlag(BEFORE_CONTEXT_DESTROY, endEventPhases) ? endEventListener : startEventListener);
}
public EventListenerTracer createEventTracer() {
return new EventListenerTracer(MetadataTracer.forClient(),
hasFlag(BEFORE_CALL_START, startEventPhases) ? startEventListener : stub,
hasFlag(AFTER_CALL_END, endEventPhases) ? endEventListener : stub,
errEventListener);
}
public <T> T build(Class<T> clientInterface, T target) {
ProxyFactory proxyFactory = createProxyFactory();
return proxyFactory.getInstance(clientInterface, target);
}
private boolean hasFlag(int test, int flags) {
return (test & flags) != 0;
}
}
}

View File

@ -0,0 +1,22 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.provider.ClientProviderControl;
import java.net.URI;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientBuilder {
ClientBuilder withAddress(URI address);
ClientBuilder withEventListener(ClientEventListener listener);
ClientBuilder withIdGenerator(IdGenerator generator);
<T> T build(Class<T> clientInterface);
<T> T build(Class<T> clientInterface, ClientProviderControl configurator);
}

View File

@ -0,0 +1,18 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.generator.IdGenerator;
import java.beans.EventHandler;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServiceBuilder {
ServiceBuilder withEventHandler(EventHandler handler);
ServiceBuilder withIdGenerator(IdGenerator generator);
<T> T build(Class<T> serviceInterface);
<T> T build(Class<T> serviceInterface, ServiceConfigurator configurator);
}

View File

@ -0,0 +1,8 @@
package com.rbkmoney.woody.api;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServiceConfigurator<T> {
void configure(T serviceProvider);
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientEventListener extends EventListener<ClientEventType, ErrorType> {
void notifyEvent(ClientEventType eventType, TraceData traceData);
void notifyError(ErrorType errorType, TraceData traceData);
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 25.04.16.
*/
public enum ClientEventType {
CALL_SERVICE,
CLIENT_SEND,
CLIENT_RECEIVE,
SERVICE_RESULT,
ERROR
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 22.04.16.
*/
public enum ErrorType {
UNKNOWN_CALL,
TRANSPORT,
ERROR_CALL
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 25.04.16.
*/
public interface EventListener<EvnT, ErrT> {
void notifyEvent(EvnT eventType, TraceData traceData);
void notifyError(ErrT errorType, TraceData traceData);
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 25.04.16.
*/
public interface ServiceEventListener extends EventListener<ServiceEventType, ErrorType> {
void notifyEvent(ServiceEventType eventType, TraceData traceData);
void notifyError(ErrorType errorType, TraceData traceData);
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 25.04.16.
*/
public enum ServiceEventType {
SERVICE_RECEIVE,
CALL_HANDLER,
HANDLER_RESULT,
SERVICE_RESULT,
ERROR
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.woody.api.generator;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface IdGenerator {
String NO_PARENT_ID = "undefined";
String generateId(long timestamp);
String generateId(long timestamp, int counter);
}

View File

@ -0,0 +1,8 @@
package com.rbkmoney.woody.api.provider;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientProviderControl<T> {
void configure(T clientProvider);
}

View File

@ -0,0 +1,82 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class HandleMethodCallerFactory implements MethodCallerFactory {
@Override
public InstanceMethodCaller getInstance(Object target, Method method) {
try {
MethodHandle mh = MethodHandles.lookup()
.findVirtual(target.getClass(), method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes())).asSpreader(Object[].class, method.getParameterCount());
return new InstanceMethodCaller() {
@Override
public Object call(Object[] args) throws Throwable {
return mh.invokeWithArguments(target, args)
;
//.unreflectSpecial(method, target.getClass())
//.invokeWithArguments(args);
//.bindTo(target)
//.invokeWithArguments(args);
}
};
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private Object[] createArgList(Object target, Object[] args) {
if (args == null || args.length == 0) {
return new Object[]{target};
} else {
Object[] mArgs = new Object[args.length + 1];
{
mArgs[0] = target;
System.arraycopy(args, 0, mArgs, 1, args.length);
return mArgs;
}
}
}
// @Override
// public InstanceMethodCaller getInstance(Object target, Method method) {
//
//
// try {
// // 1. Retrieves a Lookup
// MethodHandles.Lookup lookup = MethodHandles.lookup();
//
// MethodHandle handle=lookup.unreflect(method);
//
//
// // 4. Invoke the method
// return new InstanceMethodCaller() {
// @Override
// public Object call(Object[] args) throws Throwable {
// return handle.invokeWithArguments(args);
// }
// };
// //return args -> handle.invokeWithArguments(args);
// // ^----^ ^----^
// // | argument
// // instance of FooBar to invoke the method on
// }catch ( IllegalAccessException e) {
// e.printStackTrace();
// throw new RuntimeException(e);
// }
// }
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.proxy;
/**
* Created by vpankrashkin on 22.04.16.
*/
@FunctionalInterface
public interface InstanceMethodCaller {
Object call(Object[] args) throws Throwable;
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.proxy;
/**
* Created by vpankrashkin on 22.04.16.
*/
@FunctionalInterface
public interface MethodCallInterceptor {
Object intercept(Object[] args, InstanceMethodCaller caller) throws Throwable;
}

View File

@ -0,0 +1,24 @@
package com.rbkmoney.woody.api.proxy;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class MethodCallInterceptors {
public static MethodCallInterceptor directCallInterceptor() {
return (args, caller) -> caller.call(args);
}
public static MethodCallInterceptor trackedCallInterceptor(MethodCallTracer callTracer) {
return (args, caller) -> {
callTracer.beforeCall(args, caller);
try {
Object result = caller.call(args);
callTracer.afterCall(args, caller, result);
return result;
} catch (Throwable t) {
callTracer.callError(args, caller, t);
throw t;
}
};
}
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.woody.api.proxy;
/**
* Created by vpankrashkin on 23.04.16.
*/
public interface MethodCallTracer {
void beforeCall(Object[] args, InstanceMethodCaller caller);
void afterCall(Object[] args, InstanceMethodCaller caller, Object result);
void callError(Object[] args, InstanceMethodCaller caller, Throwable error);
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.Method;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface MethodCallerFactory {
InstanceMethodCaller getInstance(Object target, Method method);
}

View File

@ -0,0 +1,89 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.stream.Stream;
import static java.lang.reflect.Modifier.isPrivate;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class MethodShadow {
public static Method[] getShadowedMethods(Class ifaceA, Collection<Class> ifacesB) {
for (Class ifaceB : ifacesB) {
Method[] shadowedMethods = getShadowedMethods(ifaceA, ifaceB);
if (shadowedMethods.length != 0) {
return shadowedMethods;
}
}
return new Method[0];
}
public static Method[] getShadowedMethods(Class ifaceA, Class ifaceB) {
Stream.of(ifaceA, ifaceB).forEach(iface -> checkInterface(iface, "Referred class is not an interface:"));
return getOverlappingMethods(ifaceA.getMethods(), ifaceB.getMethods());
}
public static Method[] getShadowedMethods(Object object, Class iface) {
checkInterface(iface, "Referred class is not an interface:");
Method[] objMethods = Arrays.stream(object.getClass().getMethods()).filter(m -> {
int mod = m.getModifiers();
return !(isPrivate(mod));
}).toArray(Method[]::new);
return getOverlappingMethods(objMethods, iface.getMethods());
}
public static boolean isSameSignature(Method methodA, Method methodB) {
return METHOD_COMPARATOR.compare(methodA, methodB) == 0;
}
public static Method[] getOverlappingMethods(Method[] aMethods, Method[] bMethods) {
return Arrays.stream(aMethods)
.filter(tm -> Arrays.stream(bMethods)
.filter(sm -> isSameSignature(tm, sm))
.findAny().isPresent())
.toArray(Method[]::new);
}
public static Method getSameMethod(Method searchMethod, Class targetClass) {
try {
return targetClass.getMethod(searchMethod.getName(), searchMethod.getParameterTypes());
} catch (NoSuchMethodException e) {
return null;
}
}
private static void checkInterface(Class cl, String errorMessage) {
if (!cl.isInterface()) {
throw new IllegalArgumentException(errorMessage + cl.getName());
}
}
public static final Comparator<Method> METHOD_COMPARATOR = (m1, m2) -> {
int currResult = m1.getName().compareTo(m2.getName());
if (currResult != 0) {
return currResult;
}
currResult = m1.getParameterCount() - m2.getParameterCount();
if (currResult != 0) {
return currResult;
}
Class[] pt1 = m1.getParameterTypes();
Class[] pt2 = m2.getParameterTypes();
for (int i = 0; i < pt1.length; i++) {
if (pt1[i] != pt2[i])
return pt1[i].hashCode() - pt2[i].hashCode();
}
return 0;
};
}

View File

@ -0,0 +1,49 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class ProxyFactory {
private final Object object = new Object();
private final MethodCallerFactory callerFactory;
private final MethodCallTracer callTracer;
private final boolean allowObjectOverriding;
public ProxyFactory(MethodCallTracer callTracer, boolean allowObjectOverriding) {
this(new ReflectionMethodCallerFactory(), callTracer, allowObjectOverriding);
}
public ProxyFactory(MethodCallerFactory callerFactory, MethodCallTracer callTracer, boolean allowObjectOverriding) {
this.callerFactory = callerFactory;
this.callTracer = callTracer;
this.allowObjectOverriding = allowObjectOverriding;
}
public <T> T getInstance(Class<T> iface, T target) {
return getInstance(iface, target, callerFactory, callTracer, allowObjectOverriding);
}
@SuppressWarnings("unchecked")
public <T> T getInstance(Class<T> iface, T target, MethodCallerFactory callerFactory, MethodCallTracer callTracer, boolean allowObjectOverriding) {
if (!allowObjectOverriding) {
Method[] overriden = MethodShadow.getShadowedMethods(object, iface);
if (overriden.length != 0) {
throw new IllegalArgumentException("Target interface " + iface.getName() + "shadows Object methods:" + overriden);
}
}
return makeProxy(target, iface, callerFactory, callTracer);
}
protected <T> T makeProxy(T target, Class<T> iface, MethodCallerFactory callerFactory, MethodCallTracer callTracer) {
return (T) Proxy.newProxyInstance(
iface.getClassLoader(),
new Class[]{iface},
new ProxyInvocationHandler(target, iface, callerFactory, MethodCallInterceptors.trackedCallInterceptor(callTracer)));
}
}

View File

@ -0,0 +1,42 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.TreeMap;
public class ProxyInvocationHandler implements InvocationHandler {
private final Map<Method, InstanceMethodCaller> callMap;
private final MethodCallInterceptor callInterceptor;
private final Object target;
public ProxyInvocationHandler(Object target, Class iface, MethodCallerFactory callerFactory, MethodCallInterceptor callInterceptor) {
this.callMap = createCallMap(target, iface, callerFactory);
this.callInterceptor = callInterceptor;
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
InstanceMethodCaller methodCaller = callMap.get(method);
if (methodCaller != null) {
return callInterceptor.intercept(args, callMap.get(method));
} else {
return method.invoke(target, args);
}
}
private Map<Method, InstanceMethodCaller> createCallMap(Object target, Class iface, MethodCallerFactory callerFactory) {
if (!iface.isAssignableFrom(target.getClass())) {
throw new IllegalArgumentException("Target object class doesn't implement referred interface");
}
Map<Method, InstanceMethodCaller> callerMap = new TreeMap<>(MethodShadow.METHOD_COMPARATOR);
Method[] targetIfaceMethods = MethodShadow.getShadowedMethods(target, iface);
for (Method method : targetIfaceMethods) {
callerMap.put(MethodShadow.getSameMethod(method, iface), callerFactory.getInstance(target, method));
}
return callerMap;
}
}

View File

@ -0,0 +1,14 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.Method;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class ReflectionMethodCallerFactory implements MethodCallerFactory {
@Override
public InstanceMethodCaller getInstance(Object target, Method method) {
method.setAccessible(true);
return (args) -> method.invoke(target, args);
}
}

View File

@ -0,0 +1,30 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class AbstractSpan {
protected final Span span = new Span();
protected final Metadata metadata = new Metadata();
public Span getSpan() {
return span;
}
public Metadata getMetadata() {
return metadata;
}
public boolean isFilled() {
return span.isFilled();
}
public boolean isStarted() {
return span.isStarted();
}
public void reset() {
span.reset();
metadata.reset();
}
}

View File

@ -0,0 +1,8 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class ClientSpan extends AbstractSpan {
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 21.04.16.
*/
public interface Endpoint<S, T> {
S getSource();
T getTarget();
}

View File

@ -0,0 +1,41 @@
package com.rbkmoney.woody.api.trace;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class Metadata {
private static final int DEFAULT_INIT_SIZE = 8;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private Map<String, Object> values = createStore(DEFAULT_INIT_SIZE, DEFAULT_LOAD_FACTOR);
public <T> T getValue(String key) {
return (T) values.get(key);
}
public <T> T removeValue(String key) {
return (T) values.remove(key);
}
public <T> T putValue(String key, Object value) {
return (T) values.put(key, value);
}
public Collection<String> getKeys() {
return values.keySet();
}
public void reset() {
values = createStore(DEFAULT_INIT_SIZE, DEFAULT_LOAD_FACTOR);
}
private static Map<String, Object> createStore(int size, float loadFactor) {
return new HashMap<>(size, loadFactor);
}
}

View File

@ -0,0 +1,13 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 25.04.16.
*/
public class MetadataProperties {
public static final String CALL_ARGUMENTS = "md_call_arguments";
public static final String INSTANCE_METHOD_CALLER = "md_instance_method_caller";
public static final String CALL_RESULT = "md_call_result";
public static final String CALL_ERROR = "md_call_error";
public static final String EVENT_TYPE = "md_event_type";
}

View File

@ -0,0 +1,29 @@
package com.rbkmoney.woody.api.trace;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class ServerSpan extends AbstractSpan {
private Endpoint endpoint;
private final AtomicInteger counter = new AtomicInteger();
public Endpoint getEndpoint() {
return endpoint;
}
public void setEndpoint(Endpoint endpoint) {
this.endpoint = endpoint;
}
public AtomicInteger getCounter() {
return counter;
}
public void reset() {
super.reset();
endpoint = null;
counter.set(0);
}
}

View File

@ -0,0 +1,90 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class Span {
private String traceId;
private String name;
private String id;
private String parentId;
private long timestamp;
private long duration;
public String getTraceId() {
return traceId;
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
public boolean isFilled() {
return traceId != null;
}
public boolean isStarted() {
return isFilled() && timestamp != 0;
}
public void reset() {
traceId = null;
name = null;
id = null;
parentId = null;
timestamp = 0;
duration = 0;
}
@Override
public String toString() {
return "Span{" +
"traceId='" + traceId + '\'' +
", name='" + name + '\'' +
", id='" + id + '\'' +
", parentId='" + parentId + '\'' +
", timestamp=" + timestamp +
", duration=" + duration +
'}';
}
}

View File

@ -0,0 +1,66 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class TraceData {
private ClientSpan clientSpan = new ClientSpan();
private ServerSpan serverSpan = new ServerSpan();
public ClientSpan getClientSpan() {
return clientSpan;
}
public ServerSpan getServerSpan() {
return serverSpan;
}
/**
* Checks if {@link ServerSpan} is filled to determine root:
* - request initialized by server: span must be filled by server with data referred from client: has filled server span, it's not root by default -> false
* - request initialized by client, produced by any server request handling event: has filled server span, it's not root -> false
* - request initialized by client, not produced by any server request handling event: server span not filled, it's root -> true
*
* @return true - if root call is running; false - otherwise
*/
public boolean isRoot() {
return !serverSpan.isFilled();
}
/**
* Checks combination of client and server spans to determine current state:
* Consider this states scheme (S - server span, C - client span; 1 - if it's set, 0 - if not set, determined by checking traceId existence in corresponding span):
* <p>
* S | C
* -----
* 0 | 0
* 0 | 1
* 1 | 0
* 1 | 1
* <p>
* 0,0 and 0,1 combinations don't have server span and context in the state can't be server by default (no server span is set) - it's client state -> true
* 1,0 means that server span is created and no client span exists - it's server state -> false
* 1,1 means that both spans exist and child client call is active now because for any client request client span is cleared after call completion, so after child call state returns to (1,0) case - (1,1) is child client state -> true
* <p>
* This allows to eliminate the necessity for call processing code to be explicitly configured with expected call state. This can be figured out directly from the context in runtime.
* The only exclusion is {@link com.rbkmoney.woody.api.trace.context.TraceContext} itself. It uses already filled trace id field for server state initiazation
*
* @return true - if call is running as root client or child client call for server request handling; false - if call is running in server request handing
*/
public boolean isClient() {
return serverSpan.isFilled() ? clientSpan.isFilled() : true;
}
public AbstractSpan getActiveSpan() {
return isClient() ? clientSpan : serverSpan;
}
public AbstractSpan getSpan(boolean isClient) {
return isClient ? clientSpan : serverSpan;
}
public void reset() {
clientSpan.reset();
serverSpan.reset();
}
}

View File

@ -0,0 +1,41 @@
package com.rbkmoney.woody.api.trace.context;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
/**
* Created by vpankrashkin on 26.04.16.
*/
public class ContextTracer implements MethodCallTracer {
private final TraceContext traceContext;
private final MethodCallTracer targetTracer;
public ContextTracer(TraceContext traceContext, MethodCallTracer targetTracer) {
this.traceContext = traceContext;
this.targetTracer = targetTracer;
}
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
traceContext.init();
targetTracer.beforeCall(args, caller);
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
try {
targetTracer.afterCall(args, caller, result);
} finally {
traceContext.destroy();
}
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
try {
targetTracer.callError(args, caller, error);
} finally {
traceContext.destroy();
}
}
}

View File

@ -0,0 +1,51 @@
package com.rbkmoney.woody.api.trace.context;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
/**
* Created by vpankrashkin on 25.04.16.
*/
public class EventListenerTracer implements MethodCallTracer {
private final MethodCallTracer callTracer;
private final Runnable beforeCallListener;
private final Runnable afterCallListener;
private final Runnable errListener;
public EventListenerTracer(MethodCallTracer callTracer) {
this(callTracer, null, null, null);
}
public EventListenerTracer(MethodCallTracer callTracer, Runnable beforeCallListener, Runnable afterCallListener, Runnable errListener) {
if (callTracer == null) {
throw new NullPointerException("Tracer or listener cannot be null");
}
this.callTracer = callTracer;
this.beforeCallListener = beforeCallListener != null ? beforeCallListener : () -> {
};
this.afterCallListener = afterCallListener != null ? afterCallListener : () -> {
};
this.errListener = errListener != null ? errListener : () -> {
};
}
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
callTracer.beforeCall(args, caller);
beforeCallListener.run();
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
callTracer.afterCall(args, caller, result);
afterCallListener.run();
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
callTracer.callError(args, caller, error);
errListener.run();
}
}

View File

@ -0,0 +1,86 @@
package com.rbkmoney.woody.api.trace.context;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.MetadataProperties;
/**
* Created by vpankrashkin on 25.04.16.
*/
public class MetadataTracer implements MethodCallTracer {
private final boolean isClient;
private final boolean isAuto;
public static MetadataTracer forClient() {
return new MetadataTracer(true);
}
public static MetadataTracer forServer() {
return new MetadataTracer(true);
}
public static MetadataTracer forAuto() {
return new MetadataTracer();
}
public MetadataTracer() {
this.isAuto = true;
this.isClient = false;
}
public MetadataTracer(boolean isClient) {
this.isAuto = false;
this.isClient = isClient;
}
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
boolean isClient = isClient();
setBeforeCall(isClient ?
TraceContext.getCurrentTraceData().getClientSpan().getMetadata() :
TraceContext.getCurrentTraceData().getServerSpan().getMetadata(),
args, caller, isClient);
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
boolean isClient = isClient();
setAfterCall(isClient ?
TraceContext.getCurrentTraceData().getClientSpan().getMetadata() :
TraceContext.getCurrentTraceData().getServerSpan().getMetadata(),
args, caller, result, isClient);
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
boolean isClient = isClient();
setCallError(isClient ?
TraceContext.getCurrentTraceData().getClientSpan().getMetadata() :
TraceContext.getCurrentTraceData().getServerSpan().getMetadata(),
args, caller, error, isClient);
}
private void setBeforeCall(Metadata metadata, Object[] args, InstanceMethodCaller caller, boolean isClient) {
metadata.putValue(MetadataProperties.CALL_ARGUMENTS, args);
metadata.putValue(MetadataProperties.INSTANCE_METHOD_CALLER, caller);
metadata.putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.CALL_SERVICE : ServiceEventType.CALL_HANDLER);
}
private void setAfterCall(Metadata metadata, Object[] args, InstanceMethodCaller caller, Object result, boolean isClient) {
metadata.putValue(MetadataProperties.CALL_RESULT, result);
metadata.putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.SERVICE_RESULT : ServiceEventType.HANDLER_RESULT);
}
private void setCallError(Metadata metadata, Object[] args, InstanceMethodCaller caller, Throwable error, boolean isClient) {
metadata.putValue(MetadataProperties.CALL_ERROR, error);
metadata.putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.ERROR : ServiceEventType.ERROR);
}
private boolean isClient() {
return isAuto ? TraceContext.getCurrentTraceData().isClient() : isClient;
}
}

View File

@ -0,0 +1,161 @@
package com.rbkmoney.woody.api.trace.context;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.trace.Span;
import com.rbkmoney.woody.api.trace.TraceData;
import static com.rbkmoney.woody.api.generator.IdGenerator.NO_PARENT_ID;
/**
* Created by vpankrashkin on 25.04.16.
*/
public class TraceContext {
private final static ThreadLocal<TraceData> currentTraceData = ThreadLocal.withInitial(() -> new TraceData());
public static TraceData getCurrentTraceData() {
return currentTraceData.get();
}
public static void setCurrentTraceData(TraceData traceData) {
if (traceData == null) {
currentTraceData.remove();
} else {
currentTraceData.set(traceData);
}
}
public static void reset() {
//accept the idea that limited set of objects (cleaned TraceData) will stay bound to thread after instance death
//currently will lead to memory leak if lots of TraceContext classloads (which means lots of static thread locals) occurs in same thread
TraceData traceData = getCurrentTraceData();
if (traceData != null) {
traceData.reset();
}
}
public static TraceContext forClient(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy);
}
public static TraceContext forServer(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy);
}
private final IdGenerator idGenerator;
private final Runnable postInit;
private final Runnable preDestroy;
private final boolean isAuto;
private final boolean isClient;
public TraceContext(IdGenerator idGenerator) {
this(idGenerator, () -> {
}, () -> {
});
}
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy) {
this.idGenerator = idGenerator;
this.postInit = postInit;
this.preDestroy = preDestroy;
this.isAuto = true;
this.isClient = false;
}
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, boolean isClient) {
this.idGenerator = idGenerator;
this.postInit = postInit;
this.preDestroy = preDestroy;
this.isAuto = false;
this.isClient = isClient;
}
/**
* Server span must be already read and set, mustn't be invoked if any transport problems occurred
*/
public void init() {
TraceData traceData = getCurrentTraceData();
if (isClientInit(traceData)) {
initClientContext(traceData);
} else {
initServerContext(traceData);
}
postInit.run();
}
public void destroy() {
TraceData traceData = getCurrentTraceData();
boolean isClient = isClientDestroy(traceData);
setDuration(isClient ? traceData.getClientSpan().getSpan() : traceData.getServerSpan().getSpan());
try {
preDestroy.run();
} finally {
if (isClient) {
destroyClientContext(traceData);
} else {
destroyServerContext(traceData);
}
}
}
private void initClientContext(TraceData traceData) {
long timestamp = System.currentTimeMillis();
Span clientSpan = traceData.getClientSpan().getSpan();
Span serverSpan = traceData.getServerSpan().getSpan();
boolean root = traceData.isRoot();
String traceId = root ? idGenerator.generateId(timestamp) : serverSpan.getTraceId();
if (root) {
clientSpan.setId(traceId);
clientSpan.setParentId(NO_PARENT_ID);
} else {
clientSpan.setId(idGenerator.generateId(timestamp, traceData.getServerSpan().getCounter().incrementAndGet()));
clientSpan.setParentId(serverSpan.getId());
}
clientSpan.setTraceId(traceId);
clientSpan.setTimestamp(timestamp);
}
private void destroyClientContext(TraceData traceData) {
traceData.getClientSpan().reset();
}
private void initServerContext(TraceData traceData) {
long timestamp = System.currentTimeMillis();
traceData.getServerSpan().getSpan().setTimestamp(timestamp);
}
private void destroyServerContext(TraceData traceData) {
TraceContext.reset();
}
private void setDuration(Span span) {
span.setDuration(System.currentTimeMillis() - span.getTimestamp());
}
private boolean isClientInit(TraceData traceData) {
return isAuto ? isClientInitAuto(traceData) : isClient;
}
private boolean isClientDestroy(TraceData traceData) {
return isAuto ? isClientDestroyAuto(traceData) : isClient;
}
private boolean isClientInitAuto(TraceData traceData) {
Span serverSpan = traceData.getServerSpan().getSpan();
assert !(traceData.getClientSpan().isStarted() & traceData.getServerSpan().isStarted());
assert !(traceData.getClientSpan().isFilled() & traceData.getServerSpan().isFilled());
return serverSpan.isFilled() ? serverSpan.isStarted() : true;
}
private boolean isClientDestroyAuto(TraceData traceData) {
assert !(traceData.getClientSpan().isStarted() || traceData.getServerSpan().isStarted());
return traceData.getServerSpan().isStarted() ? traceData.getClientSpan().isStarted() : true;
}
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ClientSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientRequestInterceptor<Transport> extends RequestInterceptor<ClientSpan, Transport> {
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ServerSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientResponseInterceptor<Transport> extends ResponseInterceptor<ServerSpan, Transport> {
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.AbstractSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface RequestInterceptor<Context extends AbstractSpan, Transport> {
boolean interceptRequest(Context context, Transport spanContext);
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.AbstractSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ResponseInterceptor<Context extends AbstractSpan, Transport> {
void interceptResponse(Context context, Transport transport);
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ServerSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServerRequestInterceptor<Transport> extends RequestInterceptor<ServerSpan, Transport> {
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ServerSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServerResponseIntercepor<Transport> extends ResponseInterceptor<ServerSpan, Transport> {
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.trace.AbstractSpan;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
/**
* Created by vpankrashkin on 27.04.16.
*/
public class WrappedRequestInterceptor<Context extends AbstractSpan, Transport> implements RequestInterceptor<Context, Transport> {
private final RequestInterceptor interceptor;
private final Runnable listener;
public WrappedRequestInterceptor(RequestInterceptor interceptor, Runnable listener) {
this.interceptor = interceptor;
this.listener = listener;
}
@Override
public boolean interceptRequest(Context context, Transport spanContext) {
TraceData traceContext = TraceContext.getCurrentTraceData();
boolean isClient = traceContext.isClient();
if (interceptor.interceptRequest(context, spanContext)) {
traceContext.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.CLIENT_RECEIVE : ServiceEventType.SERVICE_RECEIVE);
listener.run();
return true;
} else {
traceContext.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.ERROR : ServiceEventType.ERROR);
return false;
}
}
}

View File

@ -0,0 +1,101 @@
package com.rbkmoney.woody.api.proxy;
import com.rbkmoney.woody.api.trace.context.EventListenerTracer;
import org.junit.Test;
import static org.junit.Assert.assertSame;
/**
* Created by vpankrashkin on 23.04.16.
*/
public class TestProxyInvocationFactory {
@Test
public void testString() {
//Srv direct = () -> "test";
Srv directImpl = new Srv() {
@Override
public String getString() {
return "string";
}
};
MethodCallTracer callTracer = new MethodCallTracer() {
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
//System.out.println("Before");
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
//System.out.println("After");
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
//System.out.println("Error:" + error);
error.printStackTrace();
}
};
MethodCallTracer wrappedCallTracer = new EventListenerTracer(callTracer);
ProxyFactory reflectionProxyFactory = new ProxyFactory(new ReflectionMethodCallerFactory(), wrappedCallTracer, false);
ProxyFactory handleProxyFactory = new ProxyFactory(new HandleMethodCallerFactory(), wrappedCallTracer, false);
Srv directLambda = () -> "string";
Srv refDirectProxy = reflectionProxyFactory.getInstance(Srv.class, directImpl);
Srv refLambdaProxy = reflectionProxyFactory.getInstance(Srv.class, directLambda);
Srv handleDirectProxy = handleProxyFactory.getInstance(Srv.class, directImpl);
Srv handleLambdaProxy = handleProxyFactory.getInstance(Srv.class, directLambda);
handleDirectProxy.getString();
handleLambdaProxy.getString();
for (int i = 0; i < 1000000; i++) {
assertSame(directImpl.getString(), refDirectProxy.getString());
assertSame(directImpl.getString(), refLambdaProxy.getString());
assertSame(directImpl.getString(), handleDirectProxy.getString());
assertSame(directImpl.getString(), handleLambdaProxy.getString());
}
long start = System.currentTimeMillis();
for (int i = 0; i < 5000000; i++) {
directImpl.getString();
}
System.out.println("Direct:" + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (int i = 0; i < 5000000; i++) {
directLambda.getString();
}
System.out.println("Lambda:" + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (int i = 0; i < 5000000; i++) {
refDirectProxy.getString();
}
System.out.println("Refl Direct roxy:" + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (int i = 0; i < 5000000; i++) {
refLambdaProxy.getString();
}
System.out.println("Refl Lambda roxy:" + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (int i = 0; i < 5000000; i++) {
handleDirectProxy.getString();
}
System.out.println("Handle Direct Proxy:" + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (int i = 0; i < 5000000; i++) {
handleLambdaProxy.getString();
}
System.out.println("Handle Lambda Proxy:" + (System.currentTimeMillis() - start));
}
private interface Srv {
String getString();
}
}

15
woody-thrift/pom.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>woody</artifactId>
<groupId>com.rbkmoney.woody</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>woody-thrift</artifactId>
</project>

View File

@ -0,0 +1,24 @@
package com.rbkmoney.woody.thrift;
/**
* Created by vpankrashkin on 22.04.16.
* <p>
* Custom error call constants, specific for
*/
public enum ErrorCallType {
/**
* Any thrift errors (protocol, transport, etc).
*/
PROVIDER_ERROR,
/**
* Error which is registered in service method declaration.
*/
APPLICATION_KNOWN_ERROR,
/**
* Any other error which is not registered for calling method and doesn't refer to thrift errors.
*/
APPLICATION_UNKNOWN_ERROR
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.woody.thrift.impl.http.transport;
/**
* Created by vpankrashkin on 22.04.16.
*/
public enum TransportErrorType {
NO_DATA,
BAD_REQUEST_TYPE,
BAD_CONTENT_TYPE
}