Merge pull request #1 from gdm000/master

woody java snapshot
This commit is contained in:
gdm000 2016-05-17 14:17:24 +03:00
commit 85db6fdd97
96 changed files with 4799 additions and 2 deletions

View File

@ -1,3 +1,3 @@
# rpc
# com.rbkmoney.woody.rpc
Java реализация [Библиотеки RPC вызовов для общения между микросервисами](http://52.29.202.218/scrapyard/rpc-lib/).
Java реализация [Библиотеки RPC вызовов для общения между микросервисами](http://52.29.202.218/design/ms/platform/rpc-lib/).

36
pom.xml Normal file
View File

@ -0,0 +1,36 @@
<?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>
<properties>
<api-version>1.0.0</api-version>
</properties>
<modules>
<module>woody-api</module>
<module>woody-thrift</module>
</modules>
<build>
<pluginManagement>
<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>
</pluginManagement>
</build>
</project>

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

@ -0,0 +1,31 @@
<?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>
<version>${api-version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
</plugins>
</build>
</project>

View File

@ -0,0 +1,180 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.proxy.ProxyFactory;
import com.rbkmoney.woody.api.trace.context.*;
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;
}
protected URI getAddress() {
return address;
}
protected ClientEventListener getEventListener() {
return eventListener;
}
protected IdGenerator getIdGenerator() {
return idGenerator;
}
@Override
public <T> T build(Class<T> clientInterface) {
try {
T target = createProviderClient(clientInterface);
return createProxyClient(clientInterface, target);
} catch (Exception e) {
throw new WoodyInstantiationException(e);
}
}
protected <T> T createProxyClient(Class<T> clientInterface, T target) {
return createProxyBuilder(clientInterface).build(clientInterface, target);
}
protected ProxyBuilder createProxyBuilder(Class clientInterface) {
ProxyBuilder proxyBuilder = new ProxyBuilder();
proxyBuilder.setIdGenerator(idGenerator);
proxyBuilder.setStartEventListener(getOnCallStartEventListener());
proxyBuilder.setEndEventListener(getOnCallEndEventListener());
proxyBuilder.setErrEventListener(getErrorListener());
proxyBuilder.setMetadataExtender(getOnCallMetadataExtender(clientInterface));
return proxyBuilder;
}
abstract protected Runnable getErrorListener();
abstract protected Runnable getOnCallStartEventListener();
abstract protected Runnable getOnSendEventListener();
abstract protected Runnable getOnReceiveEventListener();
abstract protected Runnable getOnCallEndEventListener();
abstract protected MethodCallTracer getOnCallMetadataExtender(Class clientInterface);
abstract protected <T> T createProviderClient(Class<T> clientInterface);
protected static class ProxyBuilder {
public static final int EVENT_DISABLE = 0b0;
public static final int EVENT_AFTER_CONTEXT_INIT = 0b01;
public static final int EVENT_BEFORE_CONTEXT_DESTROY = 0b10;
public static final int EVENT_BEFORE_CALL_START = 0b100;
public static final int EVENT_AFTER_CALL_END = 0b1000;
private int startEventPhases;
private int endEventPhases;
private int errorEventPhases;
private boolean allowObjectOverriding = false;
private final Runnable listenerStub = () -> {
};
private final MethodCallTracer extenderStub = new EmptyTracer();
private Runnable startEventListener;
private Runnable endEventListener;
private Runnable errEventListener;
private IdGenerator idGenerator;
private MethodCallTracer metadataExtender;
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 setMetadataExtender(MethodCallTracer metadataExtender) {
this.metadataExtender = metadataExtender;
}
public void setAllowObjectOverriding(boolean allowObjectOverriding) {
this.allowObjectOverriding = allowObjectOverriding;
}
public void setStartEventPhases(int phases) {
startEventPhases = phases;
}
public void setEndEventPhases(int phases) {
endEventPhases = phases;
}
public void setErrorEventPhases(int phases) {
this.errorEventPhases = phases;
}
public <T> T build(Class<T> clientInterface, T target) {
ProxyFactory proxyFactory = createProxyFactory();
return proxyFactory.getInstance(clientInterface, target);
}
protected ProxyFactory createProxyFactory() {
return new ProxyFactory(createMethodCallTracer(), allowObjectOverriding);
}
protected MethodCallTracer createMethodCallTracer() {
return new ContextTracer(createTraceContext(), createEventTracer());
}
protected TraceContext createTraceContext() {
return TraceContext.forClient(idGenerator,
hasFlag(EVENT_AFTER_CONTEXT_INIT, startEventPhases) ? startEventListener : listenerStub,
hasFlag(EVENT_BEFORE_CONTEXT_DESTROY, endEventPhases) ? endEventListener : listenerStub,
hasFlag(EVENT_BEFORE_CONTEXT_DESTROY, errorEventPhases) ? errEventListener : listenerStub);
}
protected MethodCallTracer createEventTracer() {
return new CompositeTracer(MetadataTracer.forClient(),
metadataExtender == null ? extenderStub : metadataExtender,
new EventTracer(
hasFlag(EVENT_BEFORE_CALL_START, startEventPhases) ? startEventListener : listenerStub,
hasFlag(EVENT_AFTER_CALL_END, endEventPhases) ? endEventListener : listenerStub,
errEventListener)
);
}
private boolean hasFlag(int test, int flags) {
return (test & flags) != 0;
}
}
}

View File

@ -0,0 +1,164 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.proxy.ProxyFactory;
import com.rbkmoney.woody.api.trace.context.CompositeTracer;
import com.rbkmoney.woody.api.trace.context.EmptyTracer;
import com.rbkmoney.woody.api.trace.context.EventTracer;
import com.rbkmoney.woody.api.trace.context.MetadataTracer;
/**
* Created by vpankrashkin on 10.05.16.
*/
public abstract class AbstractServiceBuilder<Service> implements ServiceBuilder<Service> {
private ServiceEventListener eventListener;
private IdGenerator idGenerator;
@Override
public ServiceBuilder withEventListener(ServiceEventListener listener) {
this.eventListener = listener;
return this;
}
@Override
public ServiceBuilder withIdGenerator(IdGenerator generator) {
this.idGenerator = generator;
return this;
}
protected IdGenerator getIdGenerator() {
return idGenerator;
}
@Override
public <T> Service build(Class<T> serviceInterface, T serviceHandler) {
try {
T target = createProxyService(serviceInterface, serviceHandler);
return createProviderService(serviceInterface, target);
} catch (Exception e) {
throw new WoodyInstantiationException(e);
}
}
protected ServiceEventListener getEventListener() {
return eventListener;
}
abstract protected Runnable getErrorListener();
abstract protected Runnable getOnCallStartEventListener();
abstract protected Runnable getOnSendEventListener();
abstract protected Runnable getOnReceiveEventListener();
abstract protected Runnable getOnCallEndEventListener();
abstract protected MethodCallTracer getOnCallMetadataExtender(Class serviceInterface);
abstract protected <T> Service createProviderService(Class<T> serviceInterface, T handler);
protected <T> T createProxyService(Class<T> serviceInterface, T handler) {
return createProxyBuilder(serviceInterface).build(serviceInterface, handler);
}
protected ProxyBuilder createProxyBuilder(Class serviceInterface) {
ProxyBuilder proxyBuilder = new ProxyBuilder();
proxyBuilder.setIdGenerator(idGenerator);
proxyBuilder.setStartEventListener(getOnCallStartEventListener());
proxyBuilder.setEndEventListener(getOnCallEndEventListener());
proxyBuilder.setErrEventListener(getErrorListener());
proxyBuilder.setMetadataExtender(getOnCallMetadataExtender(serviceInterface));
return proxyBuilder;
}
protected static class ProxyBuilder {
public static final int EVENT_DISABLE = 0b0;
public static final int EVENT_BEFORE_CALL_START = 0b100;
public static final int EVENT_AFTER_CALL_END = 0b1000;
private int startEventPhases;
private int endEventPhases;
private int errorEventPhases;
private boolean allowObjectOverriding = false;
private final Runnable listenerStub = () -> {
};
private final MethodCallTracer extenderStub = new EmptyTracer();
private Runnable startEventListener;
private Runnable endEventListener;
private Runnable errEventListener;
private IdGenerator idGenerator;
private MethodCallTracer metadataExtender;
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 setMetadataExtender(MethodCallTracer metadataExtender) {
this.metadataExtender = metadataExtender;
}
public void setAllowObjectOverriding(boolean allowObjectOverriding) {
this.allowObjectOverriding = allowObjectOverriding;
}
public void setStartEventPhases(int phases) {
startEventPhases = phases;
}
public void setEndEventPhases(int phases) {
endEventPhases = phases;
}
public void setErrorEventPhases(int phases) {
this.errorEventPhases = phases;
}
public <T> T build(Class<T> serviceInterface, T target) {
ProxyFactory proxyFactory = createProxyFactory();
return proxyFactory.getInstance(serviceInterface, target);
}
protected ProxyFactory createProxyFactory() {
return new ProxyFactory(createMethodCallTracer(), allowObjectOverriding);
}
protected MethodCallTracer createMethodCallTracer() {
return createEventTracer();
}
protected MethodCallTracer createEventTracer() {
return new CompositeTracer(MetadataTracer.forServer(),
metadataExtender == null ? extenderStub : metadataExtender,
new EventTracer(
hasFlag(EVENT_BEFORE_CALL_START, startEventPhases) ? startEventListener : listenerStub,
hasFlag(EVENT_AFTER_CALL_END, endEventPhases) ? endEventListener : listenerStub,
errEventListener)
);
}
private boolean hasFlag(int test, int flags) {
return (test & flags) != 0;
}
}
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
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);
}

View File

@ -0,0 +1,15 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServiceBuilder<Service> {
ServiceBuilder withEventListener(ServiceEventListener listener);
ServiceBuilder withIdGenerator(IdGenerator generator);
<T> Service build(Class<T> serviceInterface, T serviceHandler);
}

View File

@ -0,0 +1,26 @@
package com.rbkmoney.woody.api;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class WoodyInstantiationException extends RuntimeException {
public WoodyInstantiationException() {
super();
}
public WoodyInstantiationException(String message) {
super(message);
}
public WoodyInstantiationException(String message, Throwable cause) {
super(message, cause);
}
public WoodyInstantiationException(Throwable cause) {
super(cause);
}
protected WoodyInstantiationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 05.05.16.
*/
public enum CallType {
CALL,
CAST
}

View File

@ -0,0 +1,24 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.ContextSpan;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class ClientEvent extends Event {
public ClientEvent(TraceData traceData) {
super(traceData);
}
@Override
public ClientEventType getEventType() {
return (ClientEventType) super.getEventType();
}
@Override
public ContextSpan getActiveSpan() {
return getTraceData().getClientSpan();
}
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientEventListener extends EventListener<ClientEvent> {
void notifyEvent(ClientEvent event);
}

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,29 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 22.04.16.
* <p>
* Custom error call constants, specific for
*/
public enum ErrorType {
/**
* 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,
/**
* Any other error
*/
OTHER
}

View File

@ -0,0 +1,81 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.*;
/**
* Created by vpankrashkin on 06.05.16.
*/
public abstract class Event {
private final TraceData traceData;
public Event(TraceData traceData) {
this.traceData = traceData;
}
public TraceData getTraceData() {
return traceData;
}
public Object getEventType() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.EVENT_TYPE);
}
public CallType getCallType() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_TYPE);
}
public String getCallName() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_NAME);
}
public Object[] getCallArguments() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_ARGUMENTS);
}
public Object getCallResult() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_RESULT);
}
public ErrorType getErrorType() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.ERROR_TYPE);
}
public String getErrorName() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.ERROR_NAME);
}
public String getErrorMessage() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.ERROR_MESSAGE);
}
public String getSpanId() {
return getActiveSpan().getSpan().getId();
}
public String getParentId() {
return getActiveSpan().getSpan().getParentId();
}
public String getTraceId() {
return getActiveSpan().getSpan().getTraceId();
}
public long getTimeStamp() {
return getActiveSpan().getSpan().getTimestamp();
}
public long getDuration() {
return getActiveSpan().getSpan().getDuration();
}
public Endpoint getEndpoint() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_ENDPOINT);
}
public boolean isSuccessfullCall() {
return !ContextUtils.hasCallErrors(getActiveSpan());
}
public abstract ContextSpan getActiveSpan();
}

View File

@ -0,0 +1,8 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 25.04.16.
*/
public interface EventListener<E extends Event> {
void notifyEvent(E event);
}

View File

@ -0,0 +1,23 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.ContextSpan;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class ServiceEvent extends Event {
public ServiceEvent(TraceData traceData) {
super(traceData);
}
@Override
public ServiceEventType getEventType() {
return (ServiceEventType) super.getEventType();
}
@Override
public ContextSpan getActiveSpan() {
return getTraceData().getServiceSpan();
}
}

View File

@ -0,0 +1,8 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 25.04.16.
*/
public interface ServiceEventListener extends EventListener<ServiceEvent> {
void notifyEvent(ServiceEvent event);
}

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,27 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 04.05.16.
*/
public class BasicCommonInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private RequestInterceptor<ReqProvider> requestInterceptor;
private ResponseInterceptor<RespProvider> responseInterceptor;
public BasicCommonInterceptor(RequestInterceptor<ReqProvider> requestInterceptor, ResponseInterceptor<RespProvider> responseInterceptor) {
this.requestInterceptor = requestInterceptor == null ? new EmptyCommonInterceptor() : requestInterceptor;
this.responseInterceptor = responseInterceptor == null ? new EmptyCommonInterceptor() : responseInterceptor;
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
return requestInterceptor.interceptRequest(traceData, providerContext, contextParams);
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
return responseInterceptor.interceptResponse(traceData, providerContext, contextParams);
}
}

View File

@ -0,0 +1,7 @@
package com.rbkmoney.woody.api.interceptor;
/**
* Created by vpankrashkin on 28.04.16.
*/
public interface CommonInterceptor<ReqProvider, RespProvider> extends RequestInterceptor<ReqProvider>, ResponseInterceptor<RespProvider> {
}

View File

@ -0,0 +1,46 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 28.04.16.
*/
public class CompositeInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final CommonInterceptor[] interceptors;
private final boolean breakOnError;
public CompositeInterceptor(boolean breakOnError, CommonInterceptor... interceptors) {
this.breakOnError = breakOnError;
this.interceptors = interceptors.clone();
}
public CompositeInterceptor(CommonInterceptor... interceptors) {
this(true, interceptors);
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
boolean successful = true;
for (int i = 0; i < interceptors.length; ++i) {
successful &= interceptors[i].interceptRequest(traceData, providerContext, contextParams);
if (!successful && breakOnError) {
return false;
}
}
return successful;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
boolean successful = true;
for (int i = 0; i < interceptors.length; ++i) {
successful &= interceptors[i].interceptResponse(traceData, providerContext, contextParams);
if (!successful && breakOnError) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,34 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
/**
* Created by vpankrashkin on 10.05.16.
*/
public class ContextInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final TraceContext traceContext;
private final CommonInterceptor interceptor;
public ContextInterceptor(TraceContext traceContext, CommonInterceptor interceptor) {
this.traceContext = traceContext;
this.interceptor = interceptor;
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
traceContext.init();
return interceptor.interceptRequest(traceData, providerContext, contextParams);
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
try {
interceptor.interceptResponse(traceData, providerContext, contextParams);
} finally {
traceContext.destroy(ContextUtils.hasCallErrors(traceData.getActiveSpan()));
}
return false;
}
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class EmptyCommonInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
return true;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
return true;
}
}

View File

@ -0,0 +1,13 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface RequestInterceptor<Provider> {
/**
* @return true - if request is successfully intercepted and ready for further processing; false - if interception failed and processing must be switched to request err handling
*/
boolean interceptRequest(TraceData traceData, Provider providerContext, Object... contextParams);
}

View File

@ -0,0 +1,13 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ResponseInterceptor<Provider> {
/**
* @return true - if response is successfully intercepted and ready for further processing; false - if interception failed and processing must be switched to response err handling
*/
boolean interceptResponse(TraceData traceData, Provider providerContext, Object... contextParams);
}

View File

@ -0,0 +1,37 @@
package com.rbkmoney.woody.api.provider;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 27.04.16.
*/
public class ProviderEventInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final Runnable reqListener;
private final Runnable respListener;
public ProviderEventInterceptor(Runnable reqListener, Runnable respListener) {
this.reqListener = reqListener != null ? reqListener : () -> {
};
this.respListener = respListener != null ? respListener : () -> {
};
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.CALL_SERVICE : ServiceEventType.SERVICE_RECEIVE);
reqListener.run();
return true;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.SERVICE_RESULT : ServiceEventType.HANDLER_RESULT);
respListener.run();
return true;
}
}

View File

@ -0,0 +1,49 @@
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(method) {
@Override
public Object call(Object[] args) throws Throwable {
return mh.invokeWithArguments(target, 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;
}
}
}
}

View File

@ -0,0 +1,21 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.Method;
/**
* Created by vpankrashkin on 22.04.16.
*/
public abstract class InstanceMethodCaller {
private final Method targetMethod;
public InstanceMethodCaller(Method targetMethod) {
this.targetMethod = targetMethod;
}
public Method getTargetMethod() {
return targetMethod;
}
public abstract 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,13 @@
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,24 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.InvocationTargetException;
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 new InstanceMethodCaller(method) {
@Override
public Object call(Object[] args) throws Throwable {
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
};
}
}

View File

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

View File

@ -0,0 +1,30 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class ContextSpan {
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,64 @@
package com.rbkmoney.woody.api.trace;
import java.util.function.Function;
/**
* Created by vpankrashkin on 11.05.16.
*/
public class ContextUtils {
public static <T> T createErrIfNotIntercepted(ContextSpan span, Function<Throwable, T> errConstructor) {
Throwable err = getInterceptionError(span);
if (err != null) {
return errConstructor.apply(err);
}
return null;
}
public static Throwable getInterceptionError(ContextSpan span) {
return getMetadataParameter(span, Throwable.class, MetadataProperties.INTERCEPTION_ERROR);
}
public static void setInterceptionError(ContextSpan span, Throwable t) {
span.getMetadata().putValue(MetadataProperties.INTERCEPTION_ERROR, t);
}
public static void setCallError(ContextSpan span, Throwable t) {
span.getMetadata().putValue(MetadataProperties.CALL_ERROR, t);
}
public static Throwable getCallError(ContextSpan span) {
return getMetadataParameter(span, Throwable.class, MetadataProperties.CALL_ERROR);
}
public static boolean hasCallErrors(ContextSpan span) {
return span.getMetadata().containsKey(MetadataProperties.CALL_ERROR);
}
public static void tryThrowInterceptionError(ContextSpan span) throws Throwable {
Throwable t = getInterceptionError(span);
if (t != null) {
throw t;
}
}
public static <T> T getMetadataParameter(ContextSpan span, Class<T> targetType, String key) {
Object obj = span.getMetadata().getValue(key);
if (obj == null) {
return null;
} else if (targetType.isAssignableFrom(obj.getClass())) {
return (T) obj;
}
return null;
}
public static <T> T getContextParameter(Class<T> targetType, Object[] params, int index) {
if (params == null || params.length <= index || params[index] == null) {
return null;
}
if (targetType.isAssignableFrom(params[index].getClass())) {
return (T) params[index];
}
return null;
}
}

View File

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

View File

@ -0,0 +1,45 @@
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 = 16;
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 boolean containsKey(String key) {
return values.containsKey(key);
}
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,25 @@
package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 25.04.16.
*/
public class MetadataProperties {
public static final String INSTANCE_METHOD_CALLER = "md_instance_method_caller";
public static final String CALL_ARGUMENTS = "md_call_arguments";
public static final String CALL_RESULT = "md_call_result";
public static final String CALL_ERROR = "md_call_error";
public static final String CALL_NAME = "md_call_name";
public static final String CALL_TYPE = "md_call_type";
public static final String CALL_ENDPOINT = "md_call_endpoint";
public static final String EVENT_TYPE = "md_event_type";
public static final String ERROR_TYPE = "md_error_type";
public static final String ERROR_NAME = "md_error_name";
public static final String ERROR_MESSAGE = "md_error_message";
public static final String INTERCEPTION_ERROR = "md_interception_error";
}

View File

@ -0,0 +1,20 @@
package com.rbkmoney.woody.api.trace;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class ServiceSpan extends ContextSpan {
private final AtomicInteger counter = new AtomicInteger();
public AtomicInteger getCounter() {
return counter;
}
public void reset() {
super.reset();
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 final ClientSpan clientSpan = new ClientSpan();
private final ServiceSpan serviceSpan = new ServiceSpan();
public ClientSpan getClientSpan() {
return clientSpan;
}
public ServiceSpan getServiceSpan() {
return serviceSpan;
}
/**
* Checks if {@link ServiceSpan} 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 !serviceSpan.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 serviceSpan.isFilled() ? clientSpan.isFilled() : true;
}
public ContextSpan getActiveSpan() {
return isClient() ? clientSpan : serviceSpan;
}
public ContextSpan getSpan(boolean isClient) {
return isClient ? clientSpan : serviceSpan;
}
public void reset() {
clientSpan.reset();
serviceSpan.reset();
}
}

View File

@ -0,0 +1,43 @@
package com.rbkmoney.woody.api.trace.context;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import java.util.Arrays;
import java.util.Collection;
/**
* Created by vpankrashkin on 04.05.16.
*/
public class CompositeTracer implements MethodCallTracer {
private final MethodCallTracer[] tracers;
public CompositeTracer(MethodCallTracer... callTracers) {
this(Arrays.asList(callTracers));
}
public CompositeTracer(Collection<? extends MethodCallTracer> tracers) {
this.tracers = tracers.stream().toArray(MethodCallTracer[]::new);
}
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
for (int i = 0; i < tracers.length; ++i) {
tracers[i].beforeCall(args, caller);
}
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
for (int i = 0; i < tracers.length; ++i) {
tracers[i].afterCall(args, caller, result);
}
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
for (int i = 0; i < tracers.length; ++i) {
tracers[i].callError(args, caller, error);
}
}
}

View File

@ -0,0 +1,43 @@
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.
*
* Used to control context lifecycle on interface method call and return
*/
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(true);
}
}
}

View File

@ -0,0 +1,24 @@
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 04.05.16.
*/
public class EmptyTracer implements MethodCallTracer {
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
}
}

View File

@ -0,0 +1,43 @@
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 EventTracer implements MethodCallTracer {
private final Runnable beforeCallListener;
private final Runnable afterCallListener;
private final Runnable errListener;
public EventTracer() {
this(null, null, null);
}
public EventTracer(Runnable beforeCallListener, Runnable afterCallListener, Runnable errListener) {
this.beforeCallListener = beforeCallListener != null ? beforeCallListener : () -> {
};
this.afterCallListener = afterCallListener != null ? afterCallListener : () -> {
};
this.errListener = errListener != null ? errListener : () -> {
};
}
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
beforeCallListener.run();
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
afterCallListener.run();
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
errListener.run();
}
}

View File

@ -0,0 +1,88 @@
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.ContextSpan;
import com.rbkmoney.woody.api.trace.ContextUtils;
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(false);
}
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().getServiceSpan().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().getServiceSpan().getMetadata(),
args, caller, result, isClient);
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
boolean isClient = isClient();
setCallError(isClient ?
TraceContext.getCurrentTraceData().getClientSpan() :
TraceContext.getCurrentTraceData().getServiceSpan(),
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(ContextSpan contextSpan, Object[] args, InstanceMethodCaller caller, Throwable error, boolean isClient) {
ContextUtils.setCallError(contextSpan, error);
contextSpan.getMetadata().putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.ERROR : ServiceEventType.ERROR);
}
private boolean isClient() {
return isAuto ? TraceContext.getCurrentTraceData().isClient() : isClient;
}
}

View File

@ -0,0 +1,178 @@
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, Runnable preErrDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy, preErrDestroy);
}
public static TraceContext forServer(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, Runnable preErrDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy, preErrDestroy);
}
private final IdGenerator idGenerator;
private final Runnable postInit;
private final Runnable preDestroy;
private final Runnable preErrDestroy;
private final boolean isAuto;
private final boolean isClient;
public TraceContext(IdGenerator idGenerator) {
this(idGenerator, () -> {
}, () -> {
}, () -> {
});
}
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, Runnable preErrDestroy) {
this.idGenerator = idGenerator;
this.postInit = postInit;
this.preDestroy = preDestroy;
this.preErrDestroy = preErrDestroy;
this.isAuto = true;
this.isClient = false;
}
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, Runnable preErrDestroy, boolean isClient) {
this.idGenerator = idGenerator;
this.postInit = postInit;
this.preDestroy = preDestroy;
this.preErrDestroy = preErrDestroy;
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() {
destroy(false);
}
public void destroy(boolean onError) {
TraceData traceData = getCurrentTraceData();
boolean isClient = isClientDestroy(traceData);
setDuration(isClient ? traceData.getClientSpan().getSpan() : traceData.getServiceSpan().getSpan());
try {
if (onError) {
preErrDestroy.run();
} else {
preDestroy.run();
}
} finally {
if (isClient) {
destroyClientContext(traceData);
} else {
destroyServerContext(traceData);
}
}
}
public void setDuration() {
TraceData traceData = getCurrentTraceData();
setDuration(isClient ? traceData.getClientSpan().getSpan() : traceData.getServiceSpan().getSpan());
}
private void initClientContext(TraceData traceData) {
long timestamp = System.currentTimeMillis();
Span clientSpan = traceData.getClientSpan().getSpan();
Span serverSpan = traceData.getServiceSpan().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.getServiceSpan().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.getServiceSpan().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.getServiceSpan().getSpan();
assert !(traceData.getClientSpan().isStarted() & traceData.getServiceSpan().isStarted());
assert !(traceData.getClientSpan().isFilled() & traceData.getServiceSpan().isFilled());
return serverSpan.isFilled() ? serverSpan.isStarted() : true;
}
private boolean isClientDestroyAuto(TraceData traceData) {
assert (traceData.getClientSpan().isStarted() || traceData.getServiceSpan().isStarted());
return traceData.getServiceSpan().isStarted() ? traceData.getClientSpan().isStarted() : true;
}
}

View File

@ -0,0 +1,37 @@
package com.rbkmoney.woody.api.transport;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 27.04.16.
*/
public class TransportEventInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final Runnable reqListener;
private final Runnable respListener;
public TransportEventInterceptor(Runnable reqListener, Runnable respListener) {
this.reqListener = reqListener != null ? reqListener : () -> {
};
this.respListener = respListener != null ? respListener : () -> {
};
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.CLIENT_SEND : ServiceEventType.SERVICE_RECEIVE);
reqListener.run();
return true;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.CLIENT_RECEIVE : ServiceEventType.SERVICE_RESULT);
respListener.run();
return true;
}
}

View File

@ -0,0 +1,84 @@
package com.rbkmoney.woody.api.proxy;
import com.rbkmoney.woody.api.trace.context.EventTracer;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertSame;
/**
* Created by vpankrashkin on 23.04.16.
*/
@Ignore
public class TestProxyInvocationFactory {
@Test
public void testString() {
Srv directImpl = new Srv() {
@Override
public String getString() {
return "string";
}
};
MethodCallTracer wrappedCallTracer = new EventTracer();
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();
}
}

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

@ -0,0 +1,89 @@
<?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>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-quickstart</artifactId>
<version>9.3.9.M1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.rbkmoney.woody</groupId>
<artifactId>woody-api</artifactId>
<version>${api-version}</version>
</dependency>
<dependency>
<groupId>com.rbkmoney.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.thrift</groupId>
<artifactId>thrift-maven-plugin</artifactId>
<version>1.0-forked</version>
<configuration>
<thriftExecutable>/usr/local/bin/thrift</thriftExecutable>
</configuration>
<executions>
<execution>
<id>thrift-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>thrift-test-sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>rpc-lib</finalName>
</build>
</project>

View File

@ -0,0 +1,11 @@
package com.rbkmoney.woody.thrift.impl.http;
/**
* Created by vpankrashkin on 22.04.16.
*/
public enum TErrorType {
UNKNOWN_CALL,
TRANSPORT,
PROTOCOL,
UNKNOWN
}

View File

@ -0,0 +1,149 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.AbstractClientBuilder;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.interceptor.BasicCommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CompositeInterceptor;
import com.rbkmoney.woody.api.provider.ProviderEventInterceptor;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.trace.context.EmptyTracer;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import com.rbkmoney.woody.api.transport.TransportEventInterceptor;
import com.rbkmoney.woody.thrift.impl.http.event.THClientEvent;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCMessageRequestInterceptor;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCMessageResponseInterceptor;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCRequestInterceptor;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCResponseInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransport;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Optional;
/**
* Created by vpankrashkin on 28.04.16.
*/
public class THClientBuilder extends AbstractClientBuilder {
private HttpClient httpClient = createHttpClient();
public THClientBuilder withHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
return this;
}
@Override
protected MethodCallTracer getOnCallMetadataExtender(Class clientInterface) {
return new EmptyTracer() {
THErrorMetadataExtender metadataExtender = new THErrorMetadataExtender(clientInterface);
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
metadataExtender.extendClientError(TraceContext.getCurrentTraceData());
}
};
}
@Override
protected Runnable getErrorListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallStartEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallEndEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnSendEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnReceiveEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected <T> T createProviderClient(Class<T> clientInterface) {
try {
THttpClient tHttpClient = new THttpClient(getAddress().toString(), httpClient, createTransportInterceptor());
TProtocol tProtocol = createProtocol(tHttpClient);
return createThriftClient(clientInterface, tProtocol, createMessageInterceptor());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected ProxyBuilder createProxyBuilder(Class clientInterface) {
ProxyBuilder proxyBuilder = super.createProxyBuilder(clientInterface);
proxyBuilder.setStartEventPhases(ProxyBuilder.EVENT_DISABLE);
proxyBuilder.setEndEventPhases(ProxyBuilder.EVENT_BEFORE_CONTEXT_DESTROY);
return proxyBuilder;
}
protected TProtocol createProtocol(TTransport tTransport) {
return new TCompactProtocol(tTransport);
}
protected HttpClient createHttpClient() {
return HttpClientBuilder.create().build();
}
protected CommonInterceptor createMessageInterceptor() {
return new CompositeInterceptor(
new BasicCommonInterceptor(new THCMessageRequestInterceptor(), new THCMessageResponseInterceptor()),
new ProviderEventInterceptor(getOnCallStartEventListener(), null)
);
}
protected CommonInterceptor createTransportInterceptor() {
return new CompositeInterceptor(
new BasicCommonInterceptor(new THCRequestInterceptor(), new THCResponseInterceptor()),
new TransportEventInterceptor(getOnSendEventListener(), getOnReceiveEventListener())
);
}
protected static <T> T createThriftClient(Class<T> clientIface, TProtocol tProtocol, CommonInterceptor interceptor) {
try {
Optional<? extends Class> clientClass = Arrays.stream(clientIface.getDeclaringClass().getClasses())
.filter(cl -> cl.getSimpleName().equals("Client")).findFirst();
if (!clientClass.isPresent()) {
throw new IllegalArgumentException("Client interface doesn't conform to Thrift generated class structure");
}
if (!TServiceClient.class.isAssignableFrom(clientClass.get())) {
throw new IllegalArgumentException("Client class doesn't conform to Thrift generated class structure");
}
if (!clientIface.isAssignableFrom(clientClass.get())) {
throw new IllegalArgumentException("Client class has wrong type which is not assignable to client interface");
}
Constructor constructor = clientClass.get().getConstructor(TProtocol.class);
if (constructor == null) {
throw new IllegalArgumentException("Client class doesn't have required constructor to be created");
}
TServiceClient tClient = (TServiceClient) constructor.newInstance(tProtocol);
tClient.setInterceptor(interceptor);
return (T) tClient;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Failed to create provider client", e);
}
}
private Runnable createEventRunnable(ClientEventListener eventListener) {
return () -> eventListener.notifyEvent(new THClientEvent(TraceContext.getCurrentTraceData()));
}
}

View File

@ -0,0 +1,190 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.ErrorType;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodShadow;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THRequestInterceptionException;
import org.apache.thrift.TApplicationException;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TStruct;
import org.apache.thrift.transport.TTransportException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THErrorMetadataExtender {
private static final String UNKNOWN_ERROR_MESSAGE = "thrift application exception unknown";
private final Map<Method, Class[]> errorsMap;
public THErrorMetadataExtender(Class iface) {
this.errorsMap = getDeclaredErrorsMap(iface);
}
public TraceData extendClientError(TraceData traceData) {
extendError(traceData.getClientSpan(), (traceData1 -> {
Metadata metadata = traceData.getClientSpan().getMetadata();
Throwable callErr = ContextUtils.getCallError(traceData.getClientSpan());
if (isWrappedError(callErr)) {
extendWrappedErrorMetadata(metadata, callErr);
} else {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.OTHER);
metadata.putValue(MetadataProperties.ERROR_MESSAGE, UNKNOWN_ERROR_MESSAGE);
}
return traceData1;
}));
return traceData;
}
public TraceData extendServiceError(TraceData traceData) {
extendError(traceData.getServiceSpan(), traceData1 -> {
Metadata metadata = traceData.getServiceSpan().getMetadata();
Throwable callErr = ContextUtils.getCallError(traceData.getServiceSpan());
if (isWrappedError(callErr)) {
extendWrappedErrorMetadata(metadata, callErr);
} else if (callErr instanceof THRequestInterceptionException) {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.PROVIDER_ERROR);
metadata.putValue(THMetadataProperties.TH_ERROR_TYPE, TErrorType.TRANSPORT);
metadata.putValue(THMetadataProperties.TH_ERROR_SUBTYPE, ((THRequestInterceptionException) callErr).getErrorType());
} else {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.APPLICATION_UNKNOWN_ERROR);
metadata.putValue(MetadataProperties.ERROR_MESSAGE, UNKNOWN_ERROR_MESSAGE);
}
return traceData1;
});
return traceData;
}
private void extendError(ContextSpan contextSpan, Function<ContextSpan, ContextSpan> undeclaredErrExtender) {
Metadata metadata = contextSpan.getMetadata();
Throwable callErr = ContextUtils.getCallError(contextSpan);
if (callErr == null) {
return;
}
InstanceMethodCaller caller = getCaller(metadata);
if (caller == null) {
return;
}
Throwable previousErr = ContextUtils.getMetadataParameter(contextSpan, Throwable.class, THMetadataProperties.TH_ERROR_METADATA_SOURCE);
if (callErr == previousErr) {
return;
} else {
contextSpan.getMetadata().removeValue(THMetadataProperties.TH_TRANSPORT_RESPONSE_SET);
}
metadata.putValue(MetadataProperties.ERROR_MESSAGE, callErr.getMessage());
if (isDeclaredError(callErr.getClass(), caller.getTargetMethod())) {
extendDeclaredErrorMetadata(metadata, callErr);
} else {
undeclaredErrExtender.apply(contextSpan);
}
return;
}
private void extendDeclaredErrorMetadata(Metadata metadata, Throwable err) {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.APPLICATION_KNOWN_ERROR);
metadata.putValue(MetadataProperties.ERROR_NAME, getDeclaredErrName(err));
}
private void extendWrappedErrorMetadata(Metadata metadata, Throwable err) {
ErrorType errorType;
TErrorType tErrorType = null;
String errMessage;
if (err instanceof TApplicationException) {
TApplicationException appError = (TApplicationException) err;
switch (appError.getType()) {
case TApplicationException.INTERNAL_ERROR:
errorType = ErrorType.APPLICATION_UNKNOWN_ERROR;
errMessage = UNKNOWN_ERROR_MESSAGE;
break;
case TApplicationException.PROTOCOL_ERROR:
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.PROTOCOL;
errMessage = err.getMessage();
break;
case TApplicationException.UNKNOWN_METHOD:
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.UNKNOWN_CALL;
errMessage = err.getMessage();
break;
default:
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.UNKNOWN;
errMessage = err.getMessage();
break;
}
} else if (err instanceof TTransportException) {
TTransportException trError = (TTransportException) err;
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.TRANSPORT;
errMessage = trError.getMessage();
} else {
errorType = ErrorType.OTHER;
errMessage = err.getMessage();
}
metadata.putValue(MetadataProperties.ERROR_TYPE, errorType);
metadata.putValue(MetadataProperties.ERROR_MESSAGE, errMessage);
if (tErrorType != null) {
metadata.putValue(THMetadataProperties.TH_ERROR_TYPE, tErrorType);
}
}
public boolean isDeclaredError(Class errClass, Method callMethod) {
Class[] declaredErrors = errorsMap.get(callMethod);
for (int i = 0; i < declaredErrors.length; ++i) {
if (declaredErrors[i].isAssignableFrom(errClass)) {
return true;
}
}
return false;
}
private boolean isWrappedError(Throwable t) {
return t instanceof TException;
}
private String getDeclaredErrName(Throwable t) {
//TODO optimise this
try {
Field field = t.getClass().getDeclaredField("STRUCT_DESC");
field.setAccessible(true);
Object struct = field.get(t);
if (struct instanceof TStruct) {
return ((TStruct) struct).name;
}
return null;
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
private InstanceMethodCaller getCaller(Metadata metadata) {
Object callerObj = metadata.getValue(MetadataProperties.INSTANCE_METHOD_CALLER);
return (callerObj instanceof InstanceMethodCaller) ? (InstanceMethodCaller) callerObj : null;
}
private Map<Method, Class[]> getDeclaredErrorsMap(Class iface) {
Map<Method, Class[]> errorsMap = new TreeMap<>(MethodShadow.METHOD_COMPARATOR);
Arrays.stream(iface.getMethods()).forEach(m ->
errorsMap.put(m, Arrays.stream(m.getExceptionTypes())
.filter(e -> !e.getName().equals(TException.class.getName()))
.toArray(Class[]::new))
);
return errorsMap;
}
}

View File

@ -0,0 +1,22 @@
package com.rbkmoney.woody.thrift.impl.http;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THMetadataProperties {
public static final String TH_PROPERTY_PREFIX = "md_thrift_http_";
public static final String TH_ERROR_TYPE = TH_PROPERTY_PREFIX + "error_type";
public static final String TH_ERROR_SUBTYPE = TH_PROPERTY_PREFIX + "error_subtype";
public static final String TH_RESPONSE_STATUS = TH_PROPERTY_PREFIX + "response_status";
public static final String TH_RESPONSE_MESSAGE = TH_PROPERTY_PREFIX + "response_message";
public static final String TH_CALL_MSG_TYPE = TH_PROPERTY_PREFIX + "call_msg_type";
public static final String TH_CALL_RESULT_MSG_TYPE = TH_PROPERTY_PREFIX + "call_result_msg_type";
public static final String TH_TRANSPORT_RESPONSE = TH_PROPERTY_PREFIX + "transport_response";
public static final String TH_TRANSPORT_RESPONSE_SET = TH_PROPERTY_PREFIX + "transport_response_set";
public static final String TH_ERROR_METADATA_SOURCE = TH_PROPERTY_PREFIX + "error_metadata_source";
}

View File

@ -0,0 +1,42 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TMessage;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolDecorator;
/**
* Created by vpankrashkin on 10.05.16.
*/
public class THSProtocolWrapper extends TProtocolDecorator {
private final CommonInterceptor interceptor;
/**
* Encloses the specified protocol.
*
* @param protocol All operations will be forward to this protocol. Must be non-null.
*/
public THSProtocolWrapper(TProtocol protocol, CommonInterceptor interceptor) {
super(protocol);
this.interceptor = interceptor;
}
@Override
public TMessage readMessageBegin() throws TException {
TMessage tMessage = super.readMessageBegin();
//todo process errors
interceptor.interceptRequest(TraceContext.getCurrentTraceData(), tMessage);
return tMessage;
}
@Override
public void writeMessageBegin(TMessage tMessage) throws TException {
//todo process errors
interceptor.interceptResponse(TraceContext.getCurrentTraceData(), tMessage);
super.writeMessageBegin(tMessage);
}
}

View File

@ -0,0 +1,155 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.AbstractServiceBuilder;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.api.interceptor.BasicCommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CompositeInterceptor;
import com.rbkmoney.woody.api.interceptor.ContextInterceptor;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.trace.context.EmptyTracer;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import com.rbkmoney.woody.api.transport.TransportEventInterceptor;
import com.rbkmoney.woody.thrift.impl.http.event.THServiceEvent;
import com.rbkmoney.woody.thrift.impl.http.interceptor.*;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServlet;
import javax.servlet.Servlet;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Optional;
/**
* Created by vpankrashkin on 28.04.16.
*/
public class THServiceBuilder extends AbstractServiceBuilder<Servlet> {
@Override
protected MethodCallTracer getOnCallMetadataExtender(Class serviceInterface) {
return new EmptyTracer() {
THErrorMetadataExtender metadataExtender = new THErrorMetadataExtender(serviceInterface);
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
metadataExtender.extendServiceError(TraceContext.getCurrentTraceData());
}
};
}
@Override
protected Runnable getErrorListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallStartEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallEndEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnSendEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnReceiveEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected <T> Servlet createProviderService(Class<T> serviceInterface, T handler) {
try {
THErrorMetadataExtender metadataExtender = new THErrorMetadataExtender(serviceInterface);
TProcessor tProcessor = createThriftProcessor(serviceInterface, handler);
return createThriftServlet(tProcessor, createTransportInterceptor(metadataExtender), metadataExtender);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected ProxyBuilder createProxyBuilder(Class clientInterface) {
ProxyBuilder proxyBuilder = super.createProxyBuilder(clientInterface);
proxyBuilder.setStartEventPhases(ProxyBuilder.EVENT_BEFORE_CALL_START);
proxyBuilder.setEndEventPhases(ProxyBuilder.EVENT_AFTER_CALL_END);
return proxyBuilder;
}
protected CommonInterceptor createMessageInterceptor() {
return new CompositeInterceptor(
new BasicCommonInterceptor(new THSMessageRequestInterceptor(), new THSMessageResponseInterceptor())
);
}
protected CommonInterceptor createTransportInterceptor(THErrorMetadataExtender metadataExtender) {
TraceContext traceContext = createTraceContext();
return new CompositeInterceptor(
new BasicCommonInterceptor(null, new THSResponseMetadataInterceptor(metadataExtender)),
new BasicCommonInterceptor(new THSRequestInterceptor(), new THSResponseInterceptor(true)),
new ContextInterceptor(
traceContext,
new TransportEventInterceptor(getOnReceiveEventListener(), null)
)
);
}
protected TraceContext createTraceContext() {
return TraceContext.forServer(getIdGenerator(), () -> {
}, getOnSendEventListener(), getErrorListener());
}
protected TProtocolFactory wrapProtocolFactory(TProtocolFactory tProtocolFactory, CommonInterceptor commonInterceptor) {
return tTransport -> {
TProtocol tProtocol = tProtocolFactory.getProtocol(tTransport);
return new THSProtocolWrapper(tProtocol, commonInterceptor);
};
}
protected Servlet createThriftServlet(TProcessor tProcessor, CommonInterceptor servletInterceptor, THErrorMetadataExtender metadataExtender) {
CompositeInterceptor protInterceptor = new CompositeInterceptor(
createMessageInterceptor(),
new BasicCommonInterceptor(null, new THSResponseMetadataInterceptor(metadataExtender)),
new BasicCommonInterceptor(null, new THSResponseInterceptor(false))
);
TProtocolFactory tProtocolFactory = wrapProtocolFactory(new TCompactProtocol.Factory(), protInterceptor);
return new TServlet(tProcessor, tProtocolFactory, servletInterceptor);
}
protected <T> TProcessor createThriftProcessor(Class<T> serviceInterface, T handler) {
try {
Optional<? extends Class> processorClass = Arrays.stream(serviceInterface.getDeclaringClass().getClasses())
.filter(cl -> cl.getSimpleName().equals("Processor")).findFirst();
if (!processorClass.isPresent()) {
throw new IllegalArgumentException("Service interface doesn't conform to Thrift generated class structure");
}
if (!TProcessor.class.isAssignableFrom(processorClass.get())) {
throw new IllegalArgumentException("Service class doesn't conform to Thrift generated class structure");
}
Constructor constructor = processorClass.get().getConstructor(serviceInterface);
if (constructor == null) {
throw new IllegalArgumentException("Service class doesn't have required constructor to be created");
}
TProcessor tProcessor = (TProcessor) constructor.newInstance(handler);
return tProcessor;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Failed to create provider service", e);
}
}
private Runnable createEventRunnable(ServiceEventListener eventListener) {
return () -> eventListener.notifyEvent(new THServiceEvent(TraceContext.getCurrentTraceData()));
}
}

View File

@ -0,0 +1,42 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.event.ClientEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by vpankrashkin on 12.05.16.
*/
public class ClientEventLogListener implements ClientEventListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void notifyEvent(ClientEvent event1) {
try {
THClientEvent event = (THClientEvent) event1;
switch (event.getEventType()) {
case CALL_SERVICE:
log.info("CLIENT Event: {}, Span [{}-{}-{}], [{}, Type: {}], Time: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.getCallName(), event.getCallType(), event.getTimeStamp());
break;
case CLIENT_SEND:
log.info("CLIENT Event: {}, Span [{}-{}-{}], Url: {}, Time: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.getEndpoint().getStringValue(), event.getTimeStamp());
break;
case CLIENT_RECEIVE:
log.info("CLIENT Event: {}, Span [{}-{}-{}], Status: {}, Time: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.isSuccessfullCall() ? "ok" : "error", event.getTimeStamp());
break;
case SERVICE_RESULT:
log.info("CLIENT Event: {}, Span [{}-{}-{}], Status: {}, Time: {}, Duration: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.isSuccessfullCall() ? "ok" : "error", event.getTimeStamp(), event.getDuration());
break;
case ERROR:
log.info("CLIENT Event: {}, Span [{}-{}-{}], ErrType: {}, TErrType: {}, ErrName: {}, Time: {}, Duration: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.getErrorType(), event.getThriftErrorType(), event.getErrorName(), event.getTimeStamp(), event.getDuration());
break;
default:
log.info("CLIENT Unknown error: {}", event);
break;
}
} catch (Exception e) {
log.error("Event processing failed", e);
}
}
}

View File

@ -0,0 +1,44 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ServiceEvent;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by vpankrashkin on 12.05.16.
*/
public class ServiceEventLogListener implements ServiceEventListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void notifyEvent(ServiceEvent event1) {
try {
THServiceEvent event = (THServiceEvent) event1;
switch (event.getEventType()) {
case CALL_HANDLER:
log.info("SERVER Event: {}, Span [{}-{}-{}], [{}, Type: {}], Time: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.getCallName(), event.getCallType(), event.getTimeStamp());
break;
case HANDLER_RESULT:
log.info("SERVER Event: {}, Span [{}-{}-{}], Status: {}, Time: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.isSuccessfullCall() ? "ok" : "error", event.getTimeStamp());
break;
case SERVICE_RECEIVE:
log.info("SERVER Event: {}, Span [{}-{}-{}], Status: {}, Url: {}, Time: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.isSuccessfullCall() ? "ok" : "error", event.getEndpoint().getStringValue(), event.getTimeStamp());
break;
case SERVICE_RESULT:
log.info("SERVER Event: {}, Span [{}-{}-{}], Status: {}, Time: {}, Duration: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.isSuccessfullCall() ? "ok" : "error", event.getTimeStamp(), event.getDuration());
break;
case ERROR:
log.info("SERVER Event: {}, Span [{}-{}-{}], ErrType: {}, TErrType: {}, ErrName: {}, Time: {}", event.getEventType(), event.getTraceId(), event.getSpanId(), event.getParentId(), event.getErrorType(), event.getThriftErrorType(), event.getErrorName(), event.getTimeStamp());
break;
default:
log.info("SERVER Unknown error: {}", event);
break;
}
} catch (Exception e) {
log.error("Failed to process event", e);
}
}
}

View File

@ -0,0 +1,35 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.TErrorType;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class THClientEvent extends ClientEvent {
public THClientEvent(TraceData traceData) {
super(traceData);
}
public Integer getThriftCallMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_MSG_TYPE);
}
public Integer getThiftCallResultMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE);
}
public TErrorType getThriftErrorType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_ERROR_TYPE);
}
public Integer getThriftResponseStatus() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_STATUS);
}
public String getThriftResponseMessage() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_MESSAGE);
}
}

View File

@ -0,0 +1,35 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ServiceEvent;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.TErrorType;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class THServiceEvent extends ServiceEvent {
public THServiceEvent(TraceData traceData) {
super(traceData);
}
public Integer getThriftCallMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_MSG_TYPE);
}
public Integer getThiftCallResultMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE);
}
public TErrorType getThriftErrorType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_ERROR_TYPE);
}
public Integer getThriftResponseStatus() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_STATUS);
}
public String getThriftResponseMessage() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_MESSAGE);
}
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.event.CallType;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
import org.apache.thrift.protocol.TMessageType;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THCMessageRequestInterceptor implements RequestInterceptor<TMessage> {
private final Runnable eventListener;
public THCMessageRequestInterceptor() {
this(null);
}
public THCMessageRequestInterceptor(Runnable eventListener) {
this.eventListener = eventListener == null ? () -> {
} : eventListener;
}
@Override
public boolean interceptRequest(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(MetadataProperties.CALL_NAME, providerContext.name);
metadata.putValue(MetadataProperties.CALL_TYPE, providerContext.type == TMessageType.ONEWAY ? CallType.CAST : CallType.CALL);
metadata.putValue(THMetadataProperties.TH_CALL_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,31 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THCMessageResponseInterceptor implements ResponseInterceptor<TMessage> {
private final Runnable eventListener;
public THCMessageResponseInterceptor() {
this(() -> {
});
}
public THCMessageResponseInterceptor(Runnable eventListener) {
this.eventListener = eventListener;
}
@Override
public boolean interceptResponse(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,64 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.transport.UrlStringEndpoint;
import org.apache.http.client.methods.HttpRequestBase;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THCRequestInterceptor implements RequestInterceptor {
@Override
public boolean interceptRequest(TraceData traceData, Object providerContext, Object... contextParams) {
if (providerContext instanceof HttpRequestBase) {
return interceptRequestBase(traceData.getClientSpan(), (HttpRequestBase) providerContext, contextParams);
} else if (providerContext instanceof HttpURLConnection) {
return interceptUrlConnection(traceData.getClientSpan(), (HttpURLConnection) providerContext, contextParams);
}
return interceptError(traceData, "Unknown type:" + providerContext.getClass());
}
private boolean interceptUrlConnection(ClientSpan clientSpan, HttpURLConnection connection, Object... contextParams) {
extendMetadata(clientSpan, connection.getURL().toString());
ArrayList<String[]> headers = prepareClientHeaders(clientSpan);
for (int i = 0; i < headers.size(); ++i) {
connection.setRequestProperty(headers.get(i)[0], headers.get(i)[1]);
}
return true;
}
protected boolean interceptRequestBase(ClientSpan clientSpan, HttpRequestBase requestBase, Object... contextParams) {
URL url = ContextUtils.getContextParameter(URL.class, contextParams, 0);
extendMetadata(clientSpan, url == null ? null : url.toString());
ArrayList<String[]> headers = prepareClientHeaders(clientSpan);
for (int i = 0; i < headers.size(); ++i) {
requestBase.setHeader(headers.get(i)[0], headers.get(i)[1]);
}
return true;
}
private void extendMetadata(ClientSpan clientSpan, String url) {
clientSpan.getMetadata().putValue(MetadataProperties.CALL_ENDPOINT, new UrlStringEndpoint(url));
}
private ArrayList<String[]> prepareClientHeaders(ClientSpan clientSpan) {
Span span = clientSpan.getSpan();
ArrayList<String[]> headers = new ArrayList<>();
headers.add(new String[]{"x-rbk-trace-id", span.getTraceId()});
headers.add(new String[]{"x-rbk-span-id", span.getId()});
headers.add(new String[]{"x-rbk-parent-id", span.getParentId()});
return headers;
}
private boolean interceptError(TraceData traceData, String message) {
ContextUtils.setInterceptionError(traceData.getClientSpan(), new RuntimeException(message));
return false;
}
}

View File

@ -0,0 +1,48 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.ClientSpan;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.net.HttpURLConnection;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THCResponseInterceptor implements ResponseInterceptor {
@Override
public boolean interceptResponse(TraceData traceData, Object providerContext, Object... contextParams) {
if (providerContext instanceof HttpResponse) {
return interceptResponseBase(traceData.getClientSpan(), (HttpResponse) providerContext);
} else if (providerContext instanceof HttpURLConnection) {
return interceptUrlConnection(traceData.getClientSpan(), (HttpURLConnection) providerContext);
}
return interceptError(traceData.getClientSpan(), "Unknown type:" + providerContext.getClass(), null);
}
private boolean interceptUrlConnection(ClientSpan clientSpan, HttpURLConnection connection) {
try {
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_STATUS, connection.getResponseCode());
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_MESSAGE, connection.getResponseMessage());
return true;
} catch (IOException e) {
return interceptError(clientSpan, "Failed to get response data", e);
}
}
protected boolean interceptResponseBase(ClientSpan clientSpan, HttpResponse response) {
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_STATUS, response.getStatusLine().getStatusCode());
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_MESSAGE, response.getStatusLine().getReasonPhrase());
return true;
}
private boolean interceptError(ClientSpan clientSpan, String message, Throwable cause) {
ContextUtils.setInterceptionError(clientSpan, cause);
return false;
}
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.thrift.impl.http.transport.TTransportErrorType;
/**
* Created by vpankrashkin on 11.05.16.
*/
public class THRequestInterceptionException extends RuntimeException {
private TTransportErrorType errorType;
public THRequestInterceptionException(TTransportErrorType transportErrorType) {
super();
errorType = transportErrorType;
}
public TTransportErrorType getErrorType() {
return errorType;
}
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.event.CallType;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
import org.apache.thrift.protocol.TMessageType;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THSMessageRequestInterceptor implements RequestInterceptor<TMessage> {
private final Runnable eventListener;
public THSMessageRequestInterceptor() {
this(null);
}
public THSMessageRequestInterceptor(Runnable eventListener) {
this.eventListener = eventListener == null ? () -> {
} : eventListener;
}
@Override
public boolean interceptRequest(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(MetadataProperties.CALL_NAME, providerContext.name);
metadata.putValue(MetadataProperties.CALL_TYPE, providerContext.type == TMessageType.ONEWAY ? CallType.CAST : CallType.CALL);
metadata.putValue(THMetadataProperties.TH_CALL_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,31 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THSMessageResponseInterceptor implements ResponseInterceptor<TMessage> {
private final Runnable eventListener;
public THSMessageResponseInterceptor() {
this(() -> {
});
}
public THSMessageResponseInterceptor(Runnable eventListener) {
this.eventListener = eventListener;
}
@Override
public boolean interceptResponse(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,85 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import com.rbkmoney.woody.thrift.impl.http.transport.THttpHeader;
import com.rbkmoney.woody.thrift.impl.http.transport.TTransportErrorType;
import com.rbkmoney.woody.thrift.impl.http.transport.UrlStringEndpoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THSRequestInterceptor implements RequestInterceptor {
@Override
public boolean interceptRequest(TraceData traceData, Object providerContext, Object... contextParams) {
if (providerContext instanceof HttpServletRequest) {
return interceptHttpRequest(traceData, (HttpServletRequest) providerContext, contextParams);
}
return interceptError(traceData, "Unknown type:" + providerContext.getClass());
}
protected boolean interceptHttpRequest(TraceData traceData, HttpServletRequest request, Object... contextParams) {
THttpHeader errHeader = setSpanHeaders(traceData.getServiceSpan(), request);
if (errHeader != null) {
return interceptError(traceData, new THRequestInterceptionException(TTransportErrorType.BAD_TRACE_HEADERS));
}
extendMetadata(traceData.getServiceSpan(), request, contextParams);
return true;
}
private THttpHeader setSpanHeaders(ServiceSpan serviceSpan, HttpServletRequest request) {
Span span = serviceSpan.getSpan();
String header = getSpanHeader(THttpHeader.TRACE_ID.getKeyValue(), request);
if (header == null) {
return THttpHeader.TRACE_ID;
}
span.setTraceId(header);
header = getSpanHeader(THttpHeader.SPAN_ID.getKeyValue(), request);
if (header == null) {
return THttpHeader.SPAN_ID;
}
span.setId(header);
header = getSpanHeader(THttpHeader.PARENT_ID.getKeyValue(), request);
span.setParentId(header);
return null;
}
private void extendMetadata(ServiceSpan serviceSpan, HttpServletRequest request, Object... contextParams) {
String queryString = request.getQueryString();
StringBuffer sb = request.getRequestURL();
if (queryString != null) {
sb.append('?').append(request.getQueryString());
}
serviceSpan.getMetadata().putValue(MetadataProperties.CALL_ENDPOINT, new UrlStringEndpoint(sb.toString()));
HttpServletResponse response = ContextUtils.getContextParameter(HttpServletResponse.class, contextParams, 0);
if (response != null) {
serviceSpan.getMetadata().putValue(THMetadataProperties.TH_TRANSPORT_RESPONSE, response);
}
}
private String getSpanHeader(String name, HttpServletRequest request) {
String value = request.getHeader(name);
if (value == null || value.length() == 0) {
return null;
}
return value;
}
private boolean interceptError(TraceData traceData, String message) {
return interceptError(traceData, new RuntimeException(message));
}
private boolean interceptError(TraceData traceData, Throwable t) {
ContextUtils.setInterceptionError(traceData.getServiceSpan(), t);
return false;
}
}

View File

@ -0,0 +1,146 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.event.ErrorType;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.TErrorType;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import com.rbkmoney.woody.thrift.impl.http.transport.THttpHeader;
import com.rbkmoney.woody.thrift.impl.http.transport.TTransportErrorType;
import javax.servlet.http.HttpServletResponse;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THSResponseInterceptor implements ResponseInterceptor {
public static final String THRFIT_TRANSPORT_ERROR_MSG = "thrift transport error";
public static final String THRFIT_PROTOCOL_ERROR_MSG = "thrift protocol error";
public static final String UNKNOWN_PROVIDER_ERROR_MSG = "unknown provider error";
public static final String BAD_REQUEST_HEADERS_MSG = "bad request headers";
boolean isUseContext;
public THSResponseInterceptor(boolean isUseContext) {
this.isUseContext = isUseContext;
}
@Override
public boolean interceptResponse(TraceData traceData, Object providerContext, Object... contextParams) {
if (traceData.getServiceSpan().getMetadata().containsKey(THMetadataProperties.TH_TRANSPORT_RESPONSE_SET)) {
return true;
}
HttpServletResponse response = null;
if (isUseContext) {
if (providerContext instanceof HttpServletResponse) {
response = (HttpServletResponse) providerContext;
}
} else {
response = ContextUtils.getMetadataParameter(traceData.getServiceSpan(), HttpServletResponse.class, THMetadataProperties.TH_TRANSPORT_RESPONSE);
}
if (response == null) {
return interceptError(traceData, "Unknown type:" + providerContext.getClass());
}
if (response.isCommitted()) {
return true;
}
return interceptHttpResponse(traceData.getServiceSpan(), response);
}
private boolean interceptHttpResponse(ServiceSpan serviceSpan, HttpServletResponse response) {
response.addHeader(THttpHeader.TRACE_ID.getKeyValue(), serviceSpan.getSpan().getTraceId());
response.addHeader(THttpHeader.SPAN_ID.getKeyValue(), serviceSpan.getSpan().getId());
response.addHeader(THttpHeader.PARENT_ID.getKeyValue(), serviceSpan.getSpan().getParentId());
int responseStatus;
String errLogicValue = null;
String errThriftValue = null;
Metadata metadata = serviceSpan.getMetadata();
ErrorType errType = ContextUtils.getMetadataParameter(serviceSpan, ErrorType.class, MetadataProperties.ERROR_TYPE);
if (errType != null) {
switch (errType) {
case APPLICATION_KNOWN_ERROR:
responseStatus = 200;
errLogicValue = metadata.getValue(MetadataProperties.ERROR_NAME);
break;
case PROVIDER_ERROR:
TErrorType tErrorType = ContextUtils.getMetadataParameter(serviceSpan, TErrorType.class, THMetadataProperties.TH_ERROR_TYPE);
if (tErrorType != null) {
switch (tErrorType) {
case UNKNOWN_CALL:
responseStatus = 405;
errThriftValue = "Unknown method:" + metadata.getValue(MetadataProperties.CALL_NAME);
break;
case TRANSPORT:
TTransportErrorType tTransportErrorType = ContextUtils.getMetadataParameter(serviceSpan, TTransportErrorType.class, THMetadataProperties.TH_ERROR_SUBTYPE);
if (tTransportErrorType != null) {
switch (tTransportErrorType) {
case BAD_TRACE_HEADERS:
case BAD_CONTENT_TYPE:
responseStatus = 403;
errThriftValue = BAD_REQUEST_HEADERS_MSG;
break;
default:
responseStatus = 403;
errThriftValue = THRFIT_TRANSPORT_ERROR_MSG;
}
} else {
responseStatus = 403;
errThriftValue = THRFIT_TRANSPORT_ERROR_MSG;
}
break;
case PROTOCOL:
responseStatus = 406;
errThriftValue = THRFIT_PROTOCOL_ERROR_MSG;
break;
case UNKNOWN:
default:
responseStatus = 410;
errThriftValue = UNKNOWN_PROVIDER_ERROR_MSG;
break;
}
} else {
responseStatus = 410;
errThriftValue = UNKNOWN_PROVIDER_ERROR_MSG;
}
break;
case APPLICATION_UNKNOWN_ERROR:
case OTHER:
default:
responseStatus = 500;
errThriftValue = metadata.getValue(MetadataProperties.ERROR_NAME);
break;
}
} else {
responseStatus = 200;
}
if (errLogicValue != null) {
response.addHeader(THttpHeader.ERROR_LOGIC.getKeyValue(), errLogicValue);
}
if (errThriftValue != null) {
response.addHeader(THttpHeader.ERROR_THRIFT.getKeyValue(), errThriftValue);
}
response.setStatus(responseStatus);
serviceSpan.getMetadata().putValue(THMetadataProperties.TH_TRANSPORT_RESPONSE_SET, true);
return true;
}
private boolean interceptError(TraceData traceData, String message) {
return interceptError(traceData, new RuntimeException(message));
}
private boolean interceptError(TraceData traceData, Throwable cause) {
ContextUtils.setInterceptionError(traceData.getServiceSpan(), cause);
return false;
}
}

View File

@ -0,0 +1,22 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THErrorMetadataExtender;
/**
* Created by vpankrashkin on 11.05.16.
*/
public class THSResponseMetadataInterceptor implements ResponseInterceptor {
private final THErrorMetadataExtender metadataExtender;
public THSResponseMetadataInterceptor(THErrorMetadataExtender metadataExtender) {
this.metadataExtender = metadataExtender;
}
@Override
public boolean interceptResponse(TraceData traceData, Object providerContext, Object... contextParams) {
metadataExtender.extendServiceError(traceData);
return true;
}
}

View File

@ -0,0 +1,22 @@
package com.rbkmoney.woody.thrift.impl.http.transport;
/**
* Created by vpankrashkin on 11.05.16.
*/
public enum THttpHeader {
TRACE_ID("x-rbk-trace-id"),
SPAN_ID("x-rbk-span-id"),
PARENT_ID("x-rbk-parent-id"),
ERROR_LOGIC("x-rbk-rpc-error-logic"),
ERROR_THRIFT("x-rbk-rpc-error-thrift");
private String value;
THttpHeader(String name) {
this.value = name;
}
public String getKeyValue() {
return value;
}
}

View File

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

View File

@ -0,0 +1,24 @@
package com.rbkmoney.woody.thrift.impl.http.transport;
import com.rbkmoney.woody.api.trace.Endpoint;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class UrlStringEndpoint implements Endpoint<String> {
private String url;
public UrlStringEndpoint(String url) {
this.url = url;
}
@Override
public String getStringValue() {
return url;
}
@Override
public String getValue() {
return url;
}
}

View File

@ -0,0 +1,33 @@
package com.rbkmoney.woody.rpc;
import org.apache.thrift.TException;
/**
* Created by vpankrashkin on 19.04.16.
*/
public class OwnerServiceImpl implements OwnerService.Iface {
@Override
public Owner getOwner(int id) throws TException {
return new Owner(1, "name");
}
@Override
public Owner getErrOwner(int id) throws test_error, TException {
return null;
}
@Override
public void setOwner(Owner owner) throws TException {
}
@Override
public void setOwnerOneway(Owner owner) throws TException {
}
@Override
public Owner setErrOwner(Owner owner, int id) throws TException {
return null;
}
}

View File

@ -0,0 +1,69 @@
package com.rbkmoney.woody.rpc;
/**
* Created by vpankrashkin on 19.04.16.
*/
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class TestHttp {
private Server server;
@Before
public void startJetty() throws Exception {
server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder("default", TServletExample.class);
context.addServlet(defaultServ, "/");
server.setHandler(context);
// Start Server
server.start();
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testServlet() throws TException {
String servletUrl = "http://localhost:8080/";
THttpClient thc = new THttpClient(servletUrl);
TProtocol loPFactory = new TCompactProtocol(thc);
OwnerService.Client client = new OwnerService.Client(loPFactory);
Owner bean = client.getOwner(1);
Assert.assertEquals("name", bean.getName());
}
public static class TServletExample extends TServlet {
public TServletExample() {
super(
new OwnerService.Processor(
new OwnerServiceImpl()),
new TCompactProtocol.Factory()
);
}
}
}

View File

@ -0,0 +1,88 @@
package com.rbkmoney.woody.rpc;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* Created by vpankrashkin on 19.04.16.
*/
public class TestSocket {
private static final int PORT = 7911;
@BeforeClass
@SuppressWarnings({"static-access"})
public static void startServer() throws URISyntaxException, IOException {
// Start thrift server in a seperate thread
new Thread(new ServerExample()).start();
try {
// wait for the server start up
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testExample() throws TTransportException, TException {
TTransport transport = new TSocket("localhost", PORT);
TProtocol protocol = new TBinaryProtocol(transport);
OwnerService.Client client = new OwnerService.Client(protocol);
transport.open();
Owner bean = client.getOwner(1);
transport.close();
Assert.assertEquals("name", bean.getName());
}
public static class TestHttpClient {
public static void main(String[] args) {
try {
TTransport transport = new TSocket("localhost", PORT);
TProtocol protocol = new TBinaryProtocol(transport);
OwnerService.Client client = new OwnerService.Client(protocol);
transport.open();
Owner bean = client.getOwner(1);
transport.close();
System.out.println(bean);
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
}
public static class ServerExample implements Runnable {
public static void main(String[] args) {
new Thread(new ServerExample()).run();
}
@Override
public void run() {
try {
TServerSocket serverTransport = new TServerSocket(PORT);
OwnerService.Processor processor = new OwnerService.Processor(new OwnerServiceImpl());
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
System.out.println("Starting server on port " + PORT);
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,125 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import javax.servlet.Servlet;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class AbstractTest {
private HandlerCollection handlerCollection;
protected Server server;
protected int serverPort = 8080;
protected TProcessor tProcessor;
@Before
public void startJetty() throws Exception {
server = new Server(serverPort);
HandlerCollection contextHandlerCollection = new HandlerCollection(true); // important! use parameter
// mutableWhenRunning==true
this.handlerCollection = contextHandlerCollection;
server.setHandler(contextHandlerCollection);
server.start();
}
protected void addServlet(Servlet servlet, String mapping) {
try {
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder(mapping, servlet);
context.addServlet(defaultServ, mapping);
handlerCollection.addHandler(context);
context.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
protected String getUrlString() {
return "http://localhost:" + serverPort;
}
public TServlet createTServlet(TProcessor tProcessor) {
return new TServlet(tProcessor, new TCompactProtocol.Factory());
}
public TServlet createMutableTervlet() {
return new TServlet(new TProcessor() {
@Override
public boolean process(TProtocol in, TProtocol out) throws TException {
return tProcessor.process(in, out);
}
}, new TCompactProtocol.Factory());
}
protected <T> Servlet createThrftRPCService(Class<T> iface, T handler, IdGenerator idGenerator, ServiceEventListener eventListener) {
THServiceBuilder serviceBuilder = new THServiceBuilder();
serviceBuilder.withIdGenerator(idGenerator);
serviceBuilder.withEventListener(eventListener);
return serviceBuilder.build(iface, handler);
}
protected String getUrlString(String contextPath) {
return getUrlString() + contextPath;
}
protected <T> T createThriftClient(Class<T> iface) throws TTransportException {
try {
THttpClient thc = new THttpClient(getUrlString(), HttpClientBuilder.create().build());
TProtocol tProtocol = new TCompactProtocol(thc);
return THClientBuilder.createThriftClient(iface, tProtocol, null);
} catch (TTransportException e) {
throw new RuntimeException(e);
}
}
protected <T> T createThriftRPCClient(Class<T> iface, IdGenerator idGenerator, ClientEventListener eventListener) {
return createThriftRPCClient(iface, idGenerator, eventListener, getUrlString());
}
protected <T> T createThriftRPCClient(Class<T> iface, IdGenerator idGenerator, ClientEventListener eventListener, String url) {
try {
THClientBuilder clientBuilder = new THClientBuilder();
clientBuilder.withAddress(new URI(url));
clientBuilder.withHttpClient(HttpClientBuilder.create().build());
clientBuilder.withIdGenerator(idGenerator);
clientBuilder.withEventListener(eventListener);
return clientBuilder.build(iface);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,18 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.generator.IdGenerator;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class IdGeneratorStub implements IdGenerator {
@Override
public String generateId(long timestamp) {
return Long.toString(timestamp);
}
@Override
public String generateId(long timestamp, int counter) {
return new StringBuilder().append(timestamp).append(':').append(counter).toString();
}
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.test_error;
import org.apache.thrift.TException;
/**
* Created by vpankrashkin on 19.04.16.
*/
public class OwnerServiceStub implements OwnerService.Iface {
@Override
public Owner getOwner(int id) throws TException {
return new Owner(id, "" + id);
}
@Override
public Owner getErrOwner(int id) throws test_error, TException {
throw new test_error(id);
}
@Override
public void setOwner(Owner owner) throws TException {
}
@Override
public void setOwnerOneway(Owner owner) throws TException {
}
@Override
public Owner setErrOwner(Owner owner, int id) throws TException {
throw new test_error(id);
}
}

View File

@ -0,0 +1,80 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.test_error;
import com.rbkmoney.woody.thrift.impl.http.event.ClientEventListenerImpl;
import com.rbkmoney.woody.thrift.impl.http.event.ClientEventLogListener;
import com.rbkmoney.woody.thrift.impl.http.event.ServiceEventListenerImpl;
import com.rbkmoney.woody.thrift.impl.http.event.ServiceEventLogListener;
import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransportException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import javax.servlet.Servlet;
import static java.lang.System.out;
/**
* Created by vpankrashkin on 12.05.16.
*/
@Ignore
public class TestChildRequests extends AbstractTest {
ClientEventListenerImpl clientEventListener = new ClientEventListenerImpl();
ServiceEventListenerImpl serviceEventListener = new ServiceEventListenerImpl();
OwnerService.Iface client1 = createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), new ClientEventLogListener(), getUrlString("/rpc"));
OwnerService.Iface client2 = createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), new ClientEventLogListener(), getUrlString("/rpc"));
OwnerService.Iface handler = new OwnerServiceStub() {
@Override
public Owner getErrOwner(int id) throws TException, test_error {
switch (id) {
case 0:
Owner owner = client2.getOwner(0);
client2.setOwnerOneway(owner);
return client2.getOwner(10);
case 200:
throw new test_error(200);
case 500:
throw new RuntimeException("Test");
default:
return super.getErrOwner(id);
}
}
};
Servlet servlet = createThrftRPCService(OwnerService.Iface.class, handler, new IdGeneratorStub(), new ServiceEventLogListener());
@Before
public void before() {
addServlet(servlet, "/rpc");
}
@Test
public void testEventOrder() throws TException {
out.println("Root call>");
Assert.assertEquals(new Owner(10, "10"), client1.getErrOwner(0));
out.println("<");
out.println("Root call>");
try {
client1.getErrOwner(200);
Assert.fail();
} catch (test_error e) {
}
out.println("<");
out.println("Root call>");
try {
client1.getErrOwner(500);
} catch (TTransportException e) {
}
out.println("<");
}
}

View File

@ -0,0 +1,178 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.CallType;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.event.ErrorType;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.test_error;
import com.rbkmoney.woody.thrift.impl.http.event.THClientEvent;
import org.apache.thrift.TException;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class TestClientEventHandling extends AbstractTest {
{
tProcessor = new OwnerService.Processor<>(new OwnerServiceStub() {
@Override
public Owner getOwner(int id) throws TException {
switch (id) {
case 0:
throw new RuntimeException("err");
case 1:
return new Owner(1, "name1");
default:
return new Owner(-1, "default");
}
}
@Override
public Owner getErrOwner(int id) throws test_error {
throw new test_error(id);
}
}
);
}
@Test
public void testExpectedError() {
addServlet(createMutableTervlet(), "/");
OwnerService.Iface client = (OwnerService.Iface) createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), (ClientEvent clientEvent) -> {
THClientEvent thClientEvent = (THClientEvent) clientEvent;
switch (thClientEvent.getEventType()) {
case CALL_SERVICE:
assertArrayEquals(new Object[]{0}, thClientEvent.getCallArguments());
assertEquals("getErrOwner", thClientEvent.getCallName());
assertEquals(CallType.CALL, thClientEvent.getCallType());
assertEquals(IdGenerator.NO_PARENT_ID, thClientEvent.getParentId());
assertNotNull(thClientEvent.getTraceId());
assertEquals(thClientEvent.getTraceId(), thClientEvent.getSpanId());
assertNull(thClientEvent.getEndpoint());
assertNotEquals(thClientEvent.getTimeStamp(), 0);
break;
case CLIENT_SEND:
assertEquals(getUrlString(), thClientEvent.getEndpoint().getStringValue());
break;
case CLIENT_RECEIVE:
assertEquals(new Integer(200), thClientEvent.getThriftResponseStatus());
assertEquals("OK", thClientEvent.getThriftResponseMessage());
break;
case SERVICE_RESULT:
fail("Should not be invoked on error");
break;
case ERROR:
assertFalse(thClientEvent.isSuccessfullCall());
assertEquals(ErrorType.APPLICATION_KNOWN_ERROR, thClientEvent.getErrorType());
assertEquals("test_error", thClientEvent.getErrorName());
assertNull(thClientEvent.getThriftErrorType());
break;
default:
fail();
}
});
try {
client.getErrOwner(0);
} catch (TException e) {
e.printStackTrace();
}
}
@Test
public void testGetOwnerOK() {
addServlet(createMutableTervlet(), "/");
OwnerService.Iface client = createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), (ClientEvent clientEvent) -> {
THClientEvent thClientEvent = (THClientEvent) clientEvent;
switch (thClientEvent.getEventType()) {
case CALL_SERVICE:
assertArrayEquals(new Object[]{1}, thClientEvent.getCallArguments());
assertEquals("getOwner", thClientEvent.getCallName());
assertEquals(CallType.CALL, thClientEvent.getCallType());
assertEquals(IdGenerator.NO_PARENT_ID, thClientEvent.getParentId());
assertNotNull(thClientEvent.getTraceId());
assertEquals(thClientEvent.getTraceId(), thClientEvent.getSpanId());
assertNull(thClientEvent.getEndpoint());
assertNotEquals(thClientEvent.getTimeStamp(), 0);
break;
case CLIENT_SEND:
assertEquals(getUrlString(), thClientEvent.getEndpoint().getStringValue());
break;
case CLIENT_RECEIVE:
assertEquals(new Integer(200), thClientEvent.getThriftResponseStatus());
assertEquals("OK", thClientEvent.getThriftResponseMessage());
break;
case SERVICE_RESULT:
assertEquals(new Owner(1, "name1"), thClientEvent.getCallResult());
break;
case ERROR:
default:
fail("Should not be invoked on success");
}
});
try {
client.getOwner(1);
} catch (TException e) {
e.printStackTrace();
}
}
@Test
public void testUnexpectedError() {
addServlet(createMutableTervlet(), "/");
OwnerService.Iface client = createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), (ClientEvent clientEvent) -> {
THClientEvent thClientEvent = (THClientEvent) clientEvent;
switch (thClientEvent.getEventType()) {
case CALL_SERVICE:
assertArrayEquals(new Object[]{0}, thClientEvent.getCallArguments());
assertEquals("getOwner", thClientEvent.getCallName());
assertEquals(CallType.CALL, thClientEvent.getCallType());
assertEquals(IdGenerator.NO_PARENT_ID, thClientEvent.getParentId());
assertNotNull(thClientEvent.getTraceId());
assertEquals(thClientEvent.getTraceId(), thClientEvent.getSpanId());
assertNull(thClientEvent.getEndpoint());
assertNotEquals(thClientEvent.getTimeStamp(), 0);
break;
case CLIENT_SEND:
assertEquals(getUrlString(), thClientEvent.getEndpoint().getStringValue());
break;
case CLIENT_RECEIVE:
assertEquals(new Integer(500), thClientEvent.getThriftResponseStatus());
assertEquals("Server Error", thClientEvent.getThriftResponseMessage());
break;
case SERVICE_RESULT:
fail("Should not be invoked on error");
break;
case ERROR:
assertFalse(thClientEvent.isSuccessfullCall());
assertEquals(ErrorType.PROVIDER_ERROR, thClientEvent.getErrorType());
assertEquals(TErrorType.TRANSPORT, thClientEvent.getThriftErrorType());
break;
default:
fail();
}
});
try {
client.getOwner(0);
} catch (TException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,156 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.test_error;
import com.rbkmoney.woody.thrift.impl.http.event.ClientActionListener;
import com.rbkmoney.woody.thrift.impl.http.event.ClientEventListenerImpl;
import com.rbkmoney.woody.thrift.impl.http.event.ServiceActionListener;
import com.rbkmoney.woody.thrift.impl.http.event.ServiceEventListenerImpl;
import org.apache.thrift.TException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.Servlet;
import static org.easymock.EasyMock.*;
/**
* Created by vpankrashkin on 12.05.16.
*/
public class TestEventOrder extends AbstractTest {
ClientEventListenerImpl clientEventListener = new ClientEventListenerImpl();
ServiceEventListenerImpl serviceEventListener = new ServiceEventListenerImpl();
OwnerService.Iface handler = new OwnerServiceStub() {
@Override
public Owner getErrOwner(int id) throws TException, test_error {
switch (id) {
case 500:
throw new RuntimeException("Test");
default:
return super.getErrOwner(id);
}
}
};
Servlet servlet = createThrftRPCService(OwnerService.Iface.class, handler, new IdGeneratorStub(), serviceEventListener);
OwnerService.Iface client = createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), clientEventListener, getUrlString("/rpc"));
@Before
public void before() {
addServlet(servlet, "/rpc");
}
@Test
public void testEventOrder() throws TException {
ClientActionListener clientActionListener = createStrictMock(ClientActionListener.class);
expect(clientActionListener.callService(anyObject())).andReturn(null);
expect(clientActionListener.clientSend(anyObject())).andReturn(null);
expect(clientActionListener.clientReceive(anyObject())).andReturn(null);
expect(clientActionListener.serviceResult(anyObject())).andReturn(null);
replay(clientActionListener);
ServiceActionListener serviceEventActionListener = createStrictMock(ServiceActionListener.class);
expect(serviceEventActionListener.serviceReceive(anyObject())).andReturn(null);
expect(serviceEventActionListener.callHandler(anyObject())).andReturn(null);
expect(serviceEventActionListener.handlerResult(anyObject())).andReturn(null);
expect(serviceEventActionListener.serviceResult(anyObject())).andReturn(null);
replay(serviceEventActionListener);
clientEventListener.setEventActionListener(clientActionListener);
serviceEventListener.setEventActionListener(serviceEventActionListener);
client.getOwner(0);
verify(clientActionListener);
}
@Test
public void testOneWayEventOrder() throws TException {
ClientActionListener clientActionListener = createStrictMock(ClientActionListener.class);
expect(clientActionListener.callService(anyObject())).andReturn(null);
expect(clientActionListener.clientSend(anyObject())).andReturn(null);
expect(clientActionListener.clientReceive(anyObject())).andReturn(null);
expect(clientActionListener.serviceResult(anyObject())).andReturn(null);
replay(clientActionListener);
ServiceActionListener serviceEventActionListener = createStrictMock(ServiceActionListener.class);
expect(serviceEventActionListener.serviceReceive(anyObject())).andReturn(null);
expect(serviceEventActionListener.callHandler(anyObject())).andReturn(null);
expect(serviceEventActionListener.handlerResult(anyObject())).andReturn(null);
expect(serviceEventActionListener.serviceResult(anyObject())).andReturn(null);
replay(serviceEventActionListener);
clientEventListener.setEventActionListener(clientActionListener);
serviceEventListener.setEventActionListener(serviceEventActionListener);
client.setOwnerOneway(new Owner(0, ""));
verify(clientActionListener);
}
@Test
public void testKnownErrEventOrder() throws TException {
ClientActionListener clientActionListener = createStrictMock(ClientActionListener.class);
expect(clientActionListener.callService(anyObject())).andReturn(null);
expect(clientActionListener.clientSend(anyObject())).andReturn(null);
expect(clientActionListener.clientReceive(anyObject())).andReturn(null);
expect(clientActionListener.error(anyObject())).andReturn(null);
replay(clientActionListener);
ServiceActionListener serviceEventActionListener = createStrictMock(ServiceActionListener.class);
expect(serviceEventActionListener.serviceReceive(anyObject())).andReturn(null);
expect(serviceEventActionListener.callHandler(anyObject())).andReturn(null);
expect(serviceEventActionListener.error(anyObject())).andReturn(null);
expect(serviceEventActionListener.serviceResult(anyObject())).andReturn(null);
replay(serviceEventActionListener);
clientEventListener.setEventActionListener(clientActionListener);
serviceEventListener.setEventActionListener(serviceEventActionListener);
try {
client.getErrOwner(1);
Assert.fail("Exception should be here");
} catch (test_error e) {
Assert.assertEquals(1, e.getId());
}
verify(clientActionListener);
}
@Test
public void testUnknownErrEventOrder() throws TException {
ClientActionListener clientActionListener = createStrictMock(ClientActionListener.class);
expect(clientActionListener.callService(anyObject())).andReturn(null);
expect(clientActionListener.clientSend(anyObject())).andReturn(null);
expect(clientActionListener.clientReceive(anyObject())).andReturn(null);
expect(clientActionListener.error(anyObject())).andReturn(null);
replay(clientActionListener);
ServiceActionListener serviceEventActionListener = createStrictMock(ServiceActionListener.class);
expect(serviceEventActionListener.serviceReceive(anyObject())).andReturn(null);
expect(serviceEventActionListener.callHandler(anyObject())).andReturn(null);
expect(serviceEventActionListener.error(anyObject())).andReturn(null);
expect(serviceEventActionListener.serviceResult(anyObject())).andReturn(null);
replay(serviceEventActionListener);
clientEventListener.setEventActionListener(clientActionListener);
serviceEventListener.setEventActionListener(serviceEventActionListener);
try {
client.getErrOwner(500);
Assert.fail("Exception should be here");
} catch (Exception e) {
e.printStackTrace();
}
verify(clientActionListener);
}
}

View File

@ -0,0 +1,160 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.test_error;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.stream.IntStream;
/**
* Created by vpankrashkin on 05.05.16.
*/
@Ignore
public class TestLoadErrThriftRPCClient {
private Server server;
@Before
public void startJetty() throws Exception {
server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder("default", TServletExample.class);
context.addServlet(defaultServ, "/");
server.setHandler(context);
// Start Server
server.start();
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testServlet() throws TTransportException, TException, URISyntaxException {
String servletUrl = "http://localhost:8080/";
OwnerService.Iface tClient = createThriftClient(servletUrl);
OwnerService.Iface tRPCClient = createThriftRPCClient(servletUrl);
try {
tClient.getErrOwner(0);
} catch (TException e) {
Assert.assertSame(e.getClass(), test_error.class);
//e.printStackTrace();
}
try {
tRPCClient.getErrOwner(0);
} catch (TException e) {
Assert.assertSame(e.getClass(), test_error.class);
//e.printStackTrace();
}
int testCount = 20000;
System.out.println("Start warmup");
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
System.out.println("Warmup ended.");
testCount = 10000;
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
testCount = 10000;
runThrift(testCount, tClient);
runHtriftRPC(testCount, tRPCClient);
}
private void runThrift(int testCount, OwnerService.Iface tClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tClient.getErrOwner(i);
} catch (test_error e) {
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
private void runHtriftRPC(int testCount, OwnerService.Iface tRPCClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tRPCClient.getErrOwner(i);
} catch (test_error e) {
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift RPC: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
public static class TServletExample extends TServlet {
public TServletExample() {
super(
new OwnerService.Processor(
new TestLoadErrThriftRPCClient.OwnerServiceImpl()),
new TCompactProtocol.Factory()
);
}
}
private OwnerService.Iface createThriftClient(String url) throws TTransportException {
THttpClient thc = new THttpClient(url, HttpClientBuilder.create().build());
TProtocol loPFactory = new TCompactProtocol(thc);
return new OwnerService.Client(loPFactory);
}
private OwnerService.Iface createThriftRPCClient(String url) throws URISyntaxException {
THClientBuilder clientBuilder = new THClientBuilder();
clientBuilder.withAddress(new URI(url));
clientBuilder.withHttpClient(HttpClientBuilder.create().build());
clientBuilder.withIdGenerator(new IdGenerator() {
@Override
public String generateId(long timestamp) {
return Long.toString(timestamp);
}
@Override
public String generateId(long timestamp, int counter) {
return new StringBuilder().append(timestamp).append(':').append(counter).toString();
}
});
return clientBuilder.build(OwnerService.Iface.class);
}
private static class OwnerServiceImpl extends OwnerServiceStub {
@Override
public Owner getErrOwner(int id) throws TException, test_error {
throw new test_error(id);
}
}
}

View File

@ -0,0 +1,167 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.event.ServiceEvent;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.TestHttp;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.*;
import javax.servlet.Servlet;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.stream.IntStream;
/**
* Created by vpankrashkin on 05.05.16.
*/
@Ignore
public class TestLoadThriftRPCClient {
private Server server;
@Before
public void startJetty() throws Exception {
server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder("default", TestHttp.TServletExample.class);
context.addServlet(defaultServ, "/default");
THServiceBuilder serviceBuilder = new THServiceBuilder();
serviceBuilder.withIdGenerator(new IdGeneratorStub());
serviceBuilder.withEventListener(new ServiceEventListener() {
@Override
public void notifyEvent(ServiceEvent event) {
}
});
Servlet rpcServlet = serviceBuilder.build(OwnerService.Iface.class, new OwnerServiceImpl());
ServletHolder rpcServ = new ServletHolder("rpc", rpcServlet);
context.addServlet(defaultServ, "/rpc");
server.setHandler(context);
// Start Server
server.start();
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testServlet() throws TTransportException, TException, URISyntaxException {
String defaultServletUrl = "http://localhost:8080/default";
String rpcServletUrl = "http://localhost:8080/rpc";
OwnerService.Iface tClient = createThriftClient(defaultServletUrl);
OwnerService.Iface tRPCClient = createThriftRPCClient(rpcServletUrl);
Owner bean = tClient.getOwner(1);
Assert.assertEquals("name", bean.getName());
bean = tRPCClient.getOwner(1);
Assert.assertEquals("name", bean.getName());
IntStream.range(1, 1000).forEach(i -> {
try {
tClient.getOwner(i);
tRPCClient.getOwner(i);
} catch (TException e) {
e.printStackTrace();
}
});
int testCount = 20000;
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
System.out.println("Warmup ended.");
testCount = 10000;
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
testCount = 10000;
runThrift(testCount, tClient);
runHtriftRPC(testCount, tRPCClient);
}
private void runThrift(int testCount, OwnerService.Iface tClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tClient.getOwner(i);
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
private void runHtriftRPC(int testCount, OwnerService.Iface tRPCClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tRPCClient.getOwner(i);
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift RPC: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
public static class TServletExample extends TServlet {
public TServletExample() {
super(
new OwnerService.Processor(
new OwnerServiceImpl()),
new TCompactProtocol.Factory()
);
}
}
private OwnerService.Iface createThriftClient(String url) throws TTransportException {
THttpClient thc = new THttpClient(url, HttpClientBuilder.create().build());
TProtocol loPFactory = new TCompactProtocol(thc);
return new OwnerService.Client(loPFactory);
}
private OwnerService.Iface createThriftRPCClient(String url) throws URISyntaxException {
THClientBuilder clientBuilder = new THClientBuilder();
clientBuilder.withAddress(new URI(url));
clientBuilder.withHttpClient(HttpClientBuilder.create().build());
clientBuilder.withIdGenerator(new IdGeneratorStub());
clientBuilder.withEventListener(new ClientEventListener() {
@Override
public void notifyEvent(ClientEvent event) {
}
});
return clientBuilder.build(OwnerService.Iface.class);
}
private static class OwnerServiceImpl extends OwnerServiceStub {
@Override
public Owner getOwner(int id) throws TException {
return new Owner(id, "name");
}
}
}

View File

@ -0,0 +1,20 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ClientEvent;
/**
* Created by vpankrashkin on 12.05.16.
*/
public interface ClientActionListener {
ClientEvent callService(ClientEvent event);
ClientEvent clientSend(ClientEvent event);
ClientEvent clientReceive(ClientEvent event);
ClientEvent serviceResult(ClientEvent event);
ClientEvent error(ClientEvent event);
ClientEvent undefined(ClientEvent event);
}

View File

@ -0,0 +1,52 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.event.ClientEventListener;
/**
* Created by vpankrashkin on 12.05.16.
*/
public class ClientEventListenerImpl implements ClientEventListener {
private ClientActionListener eventActionListener;
public ClientEventListenerImpl() {
}
public ClientEventListenerImpl(ClientActionListener eventActionListener) {
this.eventActionListener = eventActionListener;
}
public void setEventActionListener(ClientActionListener eventActionListener) {
this.eventActionListener = eventActionListener;
}
@Override
public void notifyEvent(ClientEvent event) {
switch (event.getEventType()) {
case CALL_SERVICE:
if (eventActionListener != null)
eventActionListener.callService(event);
break;
case CLIENT_SEND:
if (eventActionListener != null)
eventActionListener.clientSend(event);
break;
case CLIENT_RECEIVE:
if (eventActionListener != null)
eventActionListener.clientReceive(event);
break;
case SERVICE_RESULT:
if (eventActionListener != null)
eventActionListener.serviceResult(event);
break;
case ERROR:
if (eventActionListener != null)
eventActionListener.error(event);
break;
default:
if (eventActionListener != null)
eventActionListener.undefined(event);
break;
}
}
}

View File

@ -0,0 +1,20 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ServiceEvent;
/**
* Created by vpankrashkin on 12.05.16.
*/
public interface ServiceActionListener {
ServiceEvent callHandler(ServiceEvent event);
ServiceEvent handlerResult(ServiceEvent event);
ServiceEvent serviceReceive(ServiceEvent event);
ServiceEvent serviceResult(ServiceEvent event);
ServiceEvent error(ServiceEvent event);
ServiceEvent unddefined(ServiceEvent event);
}

View File

@ -0,0 +1,53 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ServiceEvent;
import com.rbkmoney.woody.api.event.ServiceEventListener;
/**
* Created by vpankrashkin on 12.05.16.
*/
public class ServiceEventListenerImpl implements ServiceEventListener {
private volatile ServiceActionListener eventActionListener;
public ServiceEventListenerImpl() {
}
public ServiceEventListenerImpl(ServiceActionListener eventActionListener) {
this.eventActionListener = eventActionListener;
}
public void setEventActionListener(ServiceActionListener eventActionListener) {
this.eventActionListener = eventActionListener;
}
@Override
public void notifyEvent(ServiceEvent event) {
switch (event.getEventType()) {
case CALL_HANDLER:
if (eventActionListener != null)
eventActionListener.callHandler(event);
break;
case HANDLER_RESULT:
if (eventActionListener != null)
eventActionListener.handlerResult(event);
break;
case SERVICE_RECEIVE:
if (eventActionListener != null)
eventActionListener.serviceReceive(event);
break;
case SERVICE_RESULT:
if (eventActionListener != null)
eventActionListener.serviceResult(event);
break;
case ERROR:
if (eventActionListener != null)
eventActionListener.error(event);
break;
default:
if (eventActionListener != null)
eventActionListener.unddefined(event);
break;
}
}
}

View File

@ -0,0 +1,6 @@
# Configure Jetty for StdErrLog Logging
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StrErrLog
# Overall Logging Level is INFO
org.eclipse.jetty.LEVEL=INFO
# Detail Logging for WebSocket
org.eclipse.jetty.websocket.LEVEL=DEBUG

View File

@ -0,0 +1,5 @@
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p - %m%n

View File

@ -0,0 +1,20 @@
namespace java com.rbkmoney.woody.rpc
typedef i32 int // We can use typedef to get pretty names for the types we are using
struct Owner {
1:int id,
2:string name
}
exception test_error {
1:int id
}
service OwnerService
{
Owner getOwner(1:int id),
Owner getErrOwner(1:int id) throws (1:test_error err),
void setOwner(1:Owner owner),
oneway void setOwnerOneway(1:Owner owner)
Owner setErrOwner(1:Owner owner, 2:int id) throws (1:test_error err)
}