diff --git a/README.md b/README.md index 9ee7fbd..1286591 100644 --- a/README.md +++ b/README.md @@ -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/). diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2edbf24 --- /dev/null +++ b/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + pom + com.rbkmoney.woody + woody + 1.0.0 + Java implementation for Woody spec + + 1.0.0 + + + + woody-api + woody-thrift + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + \ No newline at end of file diff --git a/woody-api/pom.xml b/woody-api/pom.xml new file mode 100644 index 0000000..1d86cd9 --- /dev/null +++ b/woody-api/pom.xml @@ -0,0 +1,31 @@ + + + + woody + com.rbkmoney.woody + 1.0.0 + + 4.0.0 + + woody-api + ${api-version} + jar + + + + junit + junit + 4.11 + test + + + + + + + + + + \ No newline at end of file diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/AbstractClientBuilder.java b/woody-api/src/main/java/com/rbkmoney/woody/api/AbstractClientBuilder.java new file mode 100644 index 0000000..932de12 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/AbstractClientBuilder.java @@ -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 build(Class clientInterface) { + try { + T target = createProviderClient(clientInterface); + return createProxyClient(clientInterface, target); + } catch (Exception e) { + throw new WoodyInstantiationException(e); + } + } + + protected T createProxyClient(Class 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 createProviderClient(Class 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 build(Class 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; + } + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/AbstractServiceBuilder.java b/woody-api/src/main/java/com/rbkmoney/woody/api/AbstractServiceBuilder.java new file mode 100644 index 0000000..7d5414b --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/AbstractServiceBuilder.java @@ -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 implements ServiceBuilder { + 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 Service build(Class 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 Service createProviderService(Class serviceInterface, T handler); + + + protected T createProxyService(Class 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 build(Class 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; + } + } + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/ClientBuilder.java b/woody-api/src/main/java/com/rbkmoney/woody/api/ClientBuilder.java new file mode 100644 index 0000000..a589f2e --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/ClientBuilder.java @@ -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 build(Class clientInterface); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/ServiceBuilder.java b/woody-api/src/main/java/com/rbkmoney/woody/api/ServiceBuilder.java new file mode 100644 index 0000000..62399a8 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/ServiceBuilder.java @@ -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 { + ServiceBuilder withEventListener(ServiceEventListener listener); + + ServiceBuilder withIdGenerator(IdGenerator generator); + + Service build(Class serviceInterface, T serviceHandler); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/WoodyInstantiationException.java b/woody-api/src/main/java/com/rbkmoney/woody/api/WoodyInstantiationException.java new file mode 100644 index 0000000..eb4b224 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/WoodyInstantiationException.java @@ -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); + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/CallType.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/CallType.java new file mode 100644 index 0000000..abf19d9 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/CallType.java @@ -0,0 +1,9 @@ +package com.rbkmoney.woody.api.event; + +/** + * Created by vpankrashkin on 05.05.16. + */ +public enum CallType { + CALL, + CAST +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEvent.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEvent.java new file mode 100644 index 0000000..1c655f5 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEvent.java @@ -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(); + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEventListener.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEventListener.java new file mode 100644 index 0000000..f9e32be --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEventListener.java @@ -0,0 +1,9 @@ +package com.rbkmoney.woody.api.event; + +/** + * Created by vpankrashkin on 22.04.16. + */ +public interface ClientEventListener extends EventListener { + void notifyEvent(ClientEvent event); + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEventType.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEventType.java new file mode 100644 index 0000000..7dfc7ac --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ClientEventType.java @@ -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 +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/ErrorType.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ErrorType.java new file mode 100644 index 0000000..05669f8 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ErrorType.java @@ -0,0 +1,29 @@ +package com.rbkmoney.woody.api.event; + +/** + * Created by vpankrashkin on 22.04.16. + *

+ * 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 + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/Event.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/Event.java new file mode 100644 index 0000000..c9da41b --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/Event.java @@ -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(); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/EventListener.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/EventListener.java new file mode 100644 index 0000000..68fb91a --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/EventListener.java @@ -0,0 +1,8 @@ +package com.rbkmoney.woody.api.event; + +/** + * Created by vpankrashkin on 25.04.16. + */ +public interface EventListener { + void notifyEvent(E event); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEvent.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEvent.java new file mode 100644 index 0000000..d3a156f --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEvent.java @@ -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(); + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEventListener.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEventListener.java new file mode 100644 index 0000000..7375bec --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEventListener.java @@ -0,0 +1,8 @@ +package com.rbkmoney.woody.api.event; + +/** + * Created by vpankrashkin on 25.04.16. + */ +public interface ServiceEventListener extends EventListener { + void notifyEvent(ServiceEvent event); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEventType.java b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEventType.java new file mode 100644 index 0000000..90167e8 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/event/ServiceEventType.java @@ -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 +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/generator/IdGenerator.java b/woody-api/src/main/java/com/rbkmoney/woody/api/generator/IdGenerator.java new file mode 100644 index 0000000..e76bb1f --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/generator/IdGenerator.java @@ -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); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/BasicCommonInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/BasicCommonInterceptor.java new file mode 100644 index 0000000..9e16679 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/BasicCommonInterceptor.java @@ -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 implements CommonInterceptor { + private RequestInterceptor requestInterceptor; + private ResponseInterceptor responseInterceptor; + + public BasicCommonInterceptor(RequestInterceptor requestInterceptor, ResponseInterceptor 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); + } + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/CommonInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/CommonInterceptor.java new file mode 100644 index 0000000..ec5b838 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/CommonInterceptor.java @@ -0,0 +1,7 @@ +package com.rbkmoney.woody.api.interceptor; + +/** + * Created by vpankrashkin on 28.04.16. + */ +public interface CommonInterceptor extends RequestInterceptor, ResponseInterceptor { +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/CompositeInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/CompositeInterceptor.java new file mode 100644 index 0000000..22b493f --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/CompositeInterceptor.java @@ -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 implements CommonInterceptor { + 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; + } + + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/ContextInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/ContextInterceptor.java new file mode 100644 index 0000000..4c50254 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/ContextInterceptor.java @@ -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 implements CommonInterceptor { + 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; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/EmptyCommonInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/EmptyCommonInterceptor.java new file mode 100644 index 0000000..b36596e --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/EmptyCommonInterceptor.java @@ -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 implements CommonInterceptor { + + @Override + public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) { + return true; + } + + @Override + public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) { + return true; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/RequestInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/RequestInterceptor.java new file mode 100644 index 0000000..5c8b0f1 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/RequestInterceptor.java @@ -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 { + /** + * @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); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/ResponseInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/ResponseInterceptor.java new file mode 100644 index 0000000..c16d0eb --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/interceptor/ResponseInterceptor.java @@ -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 { + /** + * @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); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/provider/ProviderEventInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/provider/ProviderEventInterceptor.java new file mode 100644 index 0000000..603eb6a --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/provider/ProviderEventInterceptor.java @@ -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 implements CommonInterceptor { + 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; + } + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/HandleMethodCallerFactory.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/HandleMethodCallerFactory.java new file mode 100644 index 0000000..706db4f --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/HandleMethodCallerFactory.java @@ -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; + } + } + + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/InstanceMethodCaller.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/InstanceMethodCaller.java new file mode 100644 index 0000000..589492e --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/InstanceMethodCaller.java @@ -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; + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallInterceptor.java new file mode 100644 index 0000000..49ac1d6 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallInterceptor.java @@ -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; +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallInterceptors.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallInterceptors.java new file mode 100644 index 0000000..d8e90ea --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallInterceptors.java @@ -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; + } + }; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallTracer.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallTracer.java new file mode 100644 index 0000000..0979d74 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallTracer.java @@ -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); + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallerFactory.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallerFactory.java new file mode 100644 index 0000000..e36af14 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodCallerFactory.java @@ -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); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodShadow.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodShadow.java new file mode 100644 index 0000000..384b1e7 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/MethodShadow.java @@ -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 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_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; + }; +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ProxyFactory.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ProxyFactory.java new file mode 100644 index 0000000..51d9442 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ProxyFactory.java @@ -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 getInstance(Class iface, T target) { + return getInstance(iface, target, callerFactory, callTracer, allowObjectOverriding); + } + + @SuppressWarnings("unchecked") + public T getInstance(Class 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 makeProxy(T target, Class iface, MethodCallerFactory callerFactory, MethodCallTracer callTracer) { + + return (T) Proxy.newProxyInstance( + iface.getClassLoader(), + new Class[]{iface}, + new ProxyInvocationHandler(target, iface, callerFactory, MethodCallInterceptors.trackedCallInterceptor(callTracer))); + + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ProxyInvocationHandler.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ProxyInvocationHandler.java new file mode 100644 index 0000000..227ca54 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ProxyInvocationHandler.java @@ -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 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 createCallMap(Object target, Class iface, MethodCallerFactory callerFactory) { + if (!iface.isAssignableFrom(target.getClass())) { + throw new IllegalArgumentException("Target object class doesn't implement referred interface"); + } + Map 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; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ReflectionMethodCallerFactory.java b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ReflectionMethodCallerFactory.java new file mode 100644 index 0000000..7652fcf --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/proxy/ReflectionMethodCallerFactory.java @@ -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(); + } + } + }; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ClientSpan.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ClientSpan.java new file mode 100644 index 0000000..ae37ccd --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ClientSpan.java @@ -0,0 +1,8 @@ +package com.rbkmoney.woody.api.trace; + +/** + * Created by vpankrashkin on 21.04.16. + */ +public class ClientSpan extends ContextSpan { + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ContextSpan.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ContextSpan.java new file mode 100644 index 0000000..0dcb801 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ContextSpan.java @@ -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(); + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ContextUtils.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ContextUtils.java new file mode 100644 index 0000000..25473c7 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ContextUtils.java @@ -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 createErrIfNotIntercepted(ContextSpan span, Function 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 getMetadataParameter(ContextSpan span, Class 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 getContextParameter(Class 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; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Endpoint.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Endpoint.java new file mode 100644 index 0000000..44dddff --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Endpoint.java @@ -0,0 +1,10 @@ +package com.rbkmoney.woody.api.trace; + +/** + * Created by vpankrashkin on 21.04.16. + */ +public interface Endpoint { + String getStringValue(); + + T getValue(); +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Metadata.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Metadata.java new file mode 100644 index 0000000..c2bdfc0 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Metadata.java @@ -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 values = createStore(DEFAULT_INIT_SIZE, DEFAULT_LOAD_FACTOR); + + public T getValue(String key) { + return (T) values.get(key); + } + + public T removeValue(String key) { + return (T) values.remove(key); + } + + public T putValue(String key, Object value) { + return (T) values.put(key, value); + } + + public boolean containsKey(String key) { + return values.containsKey(key); + } + + public Collection getKeys() { + return values.keySet(); + } + + + public void reset() { + values = createStore(DEFAULT_INIT_SIZE, DEFAULT_LOAD_FACTOR); + } + + private static Map createStore(int size, float loadFactor) { + return new HashMap<>(size, loadFactor); + } + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/MetadataProperties.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/MetadataProperties.java new file mode 100644 index 0000000..23ca884 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/MetadataProperties.java @@ -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"; + + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ServiceSpan.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ServiceSpan.java new file mode 100644 index 0000000..79eaa0c --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/ServiceSpan.java @@ -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); + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Span.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Span.java new file mode 100644 index 0000000..1019b71 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/Span.java @@ -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 + + '}'; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/TraceData.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/TraceData.java new file mode 100644 index 0000000..47834a7 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/TraceData.java @@ -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): + *

+ * S | C + * ----- + * 0 | 0 + * 0 | 1 + * 1 | 0 + * 1 | 1 + *

+ * 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 + *

+ * 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(); + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/CompositeTracer.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/CompositeTracer.java new file mode 100644 index 0000000..af9ff37 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/CompositeTracer.java @@ -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 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); + } + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/ContextTracer.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/ContextTracer.java new file mode 100644 index 0000000..ff67da8 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/ContextTracer.java @@ -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); + } + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/EmptyTracer.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/EmptyTracer.java new file mode 100644 index 0000000..38aa2a4 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/EmptyTracer.java @@ -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) { + + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/EventTracer.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/EventTracer.java new file mode 100644 index 0000000..261be1c --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/EventTracer.java @@ -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(); + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/MetadataTracer.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/MetadataTracer.java new file mode 100644 index 0000000..25d1db3 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/MetadataTracer.java @@ -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; + } +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/TraceContext.java b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/TraceContext.java new file mode 100644 index 0000000..325a8f2 --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/trace/context/TraceContext.java @@ -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 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; + + } + +} diff --git a/woody-api/src/main/java/com/rbkmoney/woody/api/transport/TransportEventInterceptor.java b/woody-api/src/main/java/com/rbkmoney/woody/api/transport/TransportEventInterceptor.java new file mode 100644 index 0000000..b8f3ead --- /dev/null +++ b/woody-api/src/main/java/com/rbkmoney/woody/api/transport/TransportEventInterceptor.java @@ -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 implements CommonInterceptor { + 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; + } + +} diff --git a/woody-api/src/test/java/com/rbkmoney/woody/api/proxy/TestProxyInvocationFactory.java b/woody-api/src/test/java/com/rbkmoney/woody/api/proxy/TestProxyInvocationFactory.java new file mode 100644 index 0000000..2d8ea1e --- /dev/null +++ b/woody-api/src/test/java/com/rbkmoney/woody/api/proxy/TestProxyInvocationFactory.java @@ -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(); + } +} diff --git a/woody-thrift/pom.xml b/woody-thrift/pom.xml new file mode 100644 index 0000000..fc06c60 --- /dev/null +++ b/woody-thrift/pom.xml @@ -0,0 +1,89 @@ + + + + woody + com.rbkmoney.woody + 1.0.0 + + 4.0.0 + + woody-thrift + + + + org.slf4j + slf4j-api + 1.7.21 + + + org.eclipse.jetty + jetty-quickstart + 9.3.9.M1 + + + junit + junit + 4.11 + test + + + + com.rbkmoney.woody + woody-api + ${api-version} + + + com.rbkmoney.thrift + libthrift + 0.9.3 + + + org.slf4j + slf4j-api + 1.7.21 + + + org.slf4j + slf4j-log4j12 + 1.7.21 + + + + org.easymock + easymock + 3.4 + test + + + + + + org.apache.thrift + thrift-maven-plugin + 1.0-forked + + /usr/local/bin/thrift + + + + thrift-sources + generate-sources + + compile + + + + thrift-test-sources + generate-test-sources + + testCompile + + + + + + rpc-lib + + \ No newline at end of file diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/TErrorType.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/TErrorType.java new file mode 100644 index 0000000..ab9597e --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/TErrorType.java @@ -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 +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THClientBuilder.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THClientBuilder.java new file mode 100644 index 0000000..57aa200 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THClientBuilder.java @@ -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 createProviderClient(Class 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 createThriftClient(Class clientIface, TProtocol tProtocol, CommonInterceptor interceptor) { + try { + Optional 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())); + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THErrorMetadataExtender.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THErrorMetadataExtender.java new file mode 100644 index 0000000..d65b650 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THErrorMetadataExtender.java @@ -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 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 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 getDeclaredErrorsMap(Class iface) { + Map 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; + } +} + + diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THMetadataProperties.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THMetadataProperties.java new file mode 100644 index 0000000..62d13f7 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THMetadataProperties.java @@ -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"; +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THSProtocolWrapper.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THSProtocolWrapper.java new file mode 100644 index 0000000..fe09881 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THSProtocolWrapper.java @@ -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); + } + + +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THServiceBuilder.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THServiceBuilder.java new file mode 100644 index 0000000..db69721 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/THServiceBuilder.java @@ -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 { + + + @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 Servlet createProviderService(Class 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 TProcessor createThriftProcessor(Class serviceInterface, T handler) { + try { + Optional 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())); + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/ClientEventLogListener.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/ClientEventLogListener.java new file mode 100644 index 0000000..10917f6 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/ClientEventLogListener.java @@ -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); + } + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceEventLogListener.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceEventLogListener.java new file mode 100644 index 0000000..546d64b --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceEventLogListener.java @@ -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); + } + + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/THClientEvent.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/THClientEvent.java new file mode 100644 index 0000000..5ace363 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/THClientEvent.java @@ -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); + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/THServiceEvent.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/THServiceEvent.java new file mode 100644 index 0000000..a9c3919 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/event/THServiceEvent.java @@ -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); + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCMessageRequestInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCMessageRequestInterceptor.java new file mode 100644 index 0000000..95a964f --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCMessageRequestInterceptor.java @@ -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 { + 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; + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCMessageResponseInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCMessageResponseInterceptor.java new file mode 100644 index 0000000..97f06ad --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCMessageResponseInterceptor.java @@ -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 { + 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; + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCRequestInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCRequestInterceptor.java new file mode 100644 index 0000000..e859838 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCRequestInterceptor.java @@ -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 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 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 prepareClientHeaders(ClientSpan clientSpan) { + Span span = clientSpan.getSpan(); + ArrayList 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; + } + +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCResponseInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCResponseInterceptor.java new file mode 100644 index 0000000..6aeb5fa --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THCResponseInterceptor.java @@ -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; + } + +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THRequestInterceptionException.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THRequestInterceptionException.java new file mode 100644 index 0000000..db0daaa --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THRequestInterceptionException.java @@ -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; + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSMessageRequestInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSMessageRequestInterceptor.java new file mode 100644 index 0000000..5ae4a45 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSMessageRequestInterceptor.java @@ -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 { + 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; + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSMessageResponseInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSMessageResponseInterceptor.java new file mode 100644 index 0000000..e61fde3 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSMessageResponseInterceptor.java @@ -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 { + 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; + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSRequestInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSRequestInterceptor.java new file mode 100644 index 0000000..da2f07a --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSRequestInterceptor.java @@ -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; + } + +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSResponseInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSResponseInterceptor.java new file mode 100644 index 0000000..a02fa7c --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSResponseInterceptor.java @@ -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; + } + +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSResponseMetadataInterceptor.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSResponseMetadataInterceptor.java new file mode 100644 index 0000000..a66efc8 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/interceptor/THSResponseMetadataInterceptor.java @@ -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; + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/THttpHeader.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/THttpHeader.java new file mode 100644 index 0000000..e0519dc --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/THttpHeader.java @@ -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; + } +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/TTransportErrorType.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/TTransportErrorType.java new file mode 100644 index 0000000..3a0eab4 --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/TTransportErrorType.java @@ -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 +} diff --git a/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/UrlStringEndpoint.java b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/UrlStringEndpoint.java new file mode 100644 index 0000000..a582a2d --- /dev/null +++ b/woody-thrift/src/main/java/com/rbkmoney/woody/thrift/impl/http/transport/UrlStringEndpoint.java @@ -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 { + private String url; + + public UrlStringEndpoint(String url) { + this.url = url; + } + + @Override + public String getStringValue() { + return url; + } + + @Override + public String getValue() { + return url; + } +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/OwnerServiceImpl.java b/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/OwnerServiceImpl.java new file mode 100644 index 0000000..a6b462f --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/OwnerServiceImpl.java @@ -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; + } +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/TestHttp.java b/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/TestHttp.java new file mode 100644 index 0000000..797ce90 --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/TestHttp.java @@ -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() + ); + } + } + + +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/TestSocket.java b/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/TestSocket.java new file mode 100644 index 0000000..7c00178 --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/rpc/TestSocket.java @@ -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(); + } + } + } + +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/AbstractTest.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/AbstractTest.java new file mode 100644 index 0000000..439021e --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/AbstractTest.java @@ -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 Servlet createThrftRPCService(Class 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 createThriftClient(Class 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 createThriftRPCClient(Class iface, IdGenerator idGenerator, ClientEventListener eventListener) { + return createThriftRPCClient(iface, idGenerator, eventListener, getUrlString()); + } + + + protected T createThriftRPCClient(Class 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); + } + } + + +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/IdGeneratorStub.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/IdGeneratorStub.java new file mode 100644 index 0000000..092617a --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/IdGeneratorStub.java @@ -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(); + } +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/OwnerServiceStub.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/OwnerServiceStub.java new file mode 100644 index 0000000..82e80b2 --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/OwnerServiceStub.java @@ -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); + } +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestChildRequests.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestChildRequests.java new file mode 100644 index 0000000..e561d0d --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestChildRequests.java @@ -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("<"); + + + } + +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestClientEventHandling.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestClientEventHandling.java new file mode 100644 index 0000000..5949fa4 --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestClientEventHandling.java @@ -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(); + } + } + +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestEventOrder.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestEventOrder.java new file mode 100644 index 0000000..c717b5b --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestEventOrder.java @@ -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); + } +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestLoadErrThriftRPCClient.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestLoadErrThriftRPCClient.java new file mode 100644 index 0000000..dcd16fe --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestLoadErrThriftRPCClient.java @@ -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); + } + } + + +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestLoadThriftRPCClient.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestLoadThriftRPCClient.java new file mode 100644 index 0000000..6207c5a --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/TestLoadThriftRPCClient.java @@ -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"); + } + } + + +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ClientActionListener.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ClientActionListener.java new file mode 100644 index 0000000..1213354 --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ClientActionListener.java @@ -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); +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ClientEventListenerImpl.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ClientEventListenerImpl.java new file mode 100644 index 0000000..3425470 --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ClientEventListenerImpl.java @@ -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; + } + } +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceActionListener.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceActionListener.java new file mode 100644 index 0000000..4a13c3b --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceActionListener.java @@ -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); +} diff --git a/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceEventListenerImpl.java b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceEventListenerImpl.java new file mode 100644 index 0000000..407e00e --- /dev/null +++ b/woody-thrift/src/test/java/com/rbkmoney/woody/thrift/impl/http/event/ServiceEventListenerImpl.java @@ -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; + + } + } +} diff --git a/woody-thrift/src/test/resources/jetty-logging.properties b/woody-thrift/src/test/resources/jetty-logging.properties new file mode 100644 index 0000000..7540e67 --- /dev/null +++ b/woody-thrift/src/test/resources/jetty-logging.properties @@ -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 \ No newline at end of file diff --git a/woody-thrift/src/test/resources/log4j.properties b/woody-thrift/src/test/resources/log4j.properties new file mode 100644 index 0000000..dd1c560 --- /dev/null +++ b/woody-thrift/src/test/resources/log4j.properties @@ -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 diff --git a/woody-thrift/src/test/thrift/testService.thrift b/woody-thrift/src/test/thrift/testService.thrift new file mode 100644 index 0000000..96afdec --- /dev/null +++ b/woody-thrift/src/test/thrift/testService.thrift @@ -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) +} \ No newline at end of file