Issues in Testing gRPC ServerInterceptor In Java - java

I have an gRPC interceptor written in java
my gRPC interceptor looks like this
public class GrpcServerInterceptor implements ServerInterceptor {
#Override
public <R, T> ServerCall.Listener<R> interceptCall(ServerCall<R, T> call,
Metadata requestHeaders, ServerCallHandler<R, T> next) {
if(call == null || next == null)
return null;
if(call != null) {
String actionName = call.getMethodDescriptor().getBareMethodName();
String serviceName = call.getMethodDescriptor().getServiceName();
State.Holder.set(State.newBuilder().withControllerName(serviceName).withActionName(actionName).withFramework("grpc").build());
}
ServerCall.Listener<R> delegate = next.startCall(call, requestHeaders);
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<R>(delegate) {
#Override
public void onHalfClose() {
try {
super.onHalfClose();
} catch (Exception e) {
call.close(Status.INTERNAL
.withCause (e)
.withDescription("error message"), new Metadata());
}
}
};
}
}
I just want to unit test for above interceptor in junit.
I am facing issues around building ServerCall, Metaddata and ServerCallHandler Objects and passing them around.
I tried to create Server Call object like below in my unit test.
ServerCall serverCall = new ForwardingServerCall() {
#Override
protected ServerCall delegate() {
return null;
}
#Override
public MethodDescriptor getMethodDescriptor() {
return MethodDescriptor.newBuilder().
setType(MethodType.UNKNOWN).
setRequestMarshaller(ProtoUtils.marshaller((StudentRequest.getDefaultInstance()))).
setResponseMarshaller(ProtoUtils.marshaller(StudentResponse.getDefaultInstance())).
setFullMethodName(generateFullMethodName("com.test.cloud.sc.grpc.backend.service.StudentServiceImpl", "getStudentInfo")).
build();
}
};
But above codeblock has issues around setting Request and Response Marshaller.
How can i unit test all the scenarios for my interceptor with minimal code setup and I don't want to start grpc server at all ?
EDIT 1
How can i improve null check handling in gRPC interceptor ?
Many Thanks

Take a look at https://github.com/grpc/grpc-java/blob/master/api/src/test/java/io/grpc/ServerInterceptorsTest.java or https://github.com/grpc/grpc-java/blob/master/core/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java to see how server interceptors are tested.
Regarding null, the grpc framework will not pass null in call or next so I don't see any reason to check. In any case consider using com.google.common.base.Preconditions.checkNotNull from Guava.

Related

How to get parent observation on Spring Boot 3 using RestTemplate

I'm migrating some REST applications to Spring Boot 3 and I'm trying to set an extra low cardinality key/value, based on the value of an incoming request header (X-Traffic-Type).
When doing it for webflux applications, everything works fine: I provide an extension of DefaultServerRequestObservationConvention and set the value based on the incoming header:
public class MyCustomServerRequestObservationConvention extends DefaultServerRequestObservationConvention {
#Override
public KeyValues getLowCardinalityKeyValues(final ServerRequestObservationContext context) {
return super.getLowCardinalityKeyValues(context).and(KeyValue.of(SupplyHubMetricsAutoConfiguration.TRAFFIC_TYPE_TAG, getTrafficType(context.getCarrier())));
}
private String getTrafficType(final ServerHttpRequest request) {
return Optional.ofNullable(request.getHeaders().getFirst("X-Traffic-Type"))
.map(String::toLowerCase)
.orElse("");
}
}
For the client side, I can simply get the traffic type from the parent observation on an extension of DefaultClientRequestObservationConvention:
public class MyCustomClientRequestObservationConvention extends DefaultClientRequestObservationConvention {
#Override
public KeyValues getLowCardinalityKeyValues(final ClientRequestObservationContext context) {
return KeyValues.of(method(context), status(context), exception(context), outcome(context), trafficType(context), qualifier(context));
}
protected KeyValue trafficType(ClientRequestObservationContext context) {
String trafficType = Optional.ofNullable(context.getParentObservation())
.map(ObservationView::getContextView)
.map(e -> e.getLowCardinalityKeyValue(TRAFFIC_TYPE_TAG))
.map(KeyValue::getValue)
.orElse(DEFAULT_TRAFFIC_TYPE);
return KeyValue.of(TRAFFIC_TYPE_TAG, trafficType);
}
}
Now, when I do the same for the MVC applications, I can set the value on the server observation but when I implement the client part by extending DefaultClientRequestObservationConvention the observation doesn't have a parent observation (which I'm expecting to be the server one):
public class MyCustomClientRequestObservationConvention extends DefaultClientRequestObservationConvention {
#Override
public KeyValues getLowCardinalityKeyValues(final ClientRequestObservationContext context) {
return KeyValues.of(method(context), status(context), exception(context), outcome(context), trafficType(context), qualifier());
}
protected KeyValue trafficType(ClientRequestObservationContext context) {
// context.getParentObservation() always returns null
String trafficType = Optional.ofNullable(context.getParentObservation())
.map(ObservationView::getContextView)
.map(e -> e.getLowCardinalityKeyValue(TRAFFIC_TYPE_TAG))
.map(KeyValue::getValue)
.orElse(DEFAULT_TRAFFIC_TYPE);
return KeyValue.of(TRAFFIC_TYPE_TAG, trafficType);
}
Am I doing something wrong here? what's the reason why webflux observations keep the hierarchy but mvc ones seems not to?

Global exception handler never get called in micronaut 3 with project reactor

After upgrading the micronaut application from 2.5.12 to 3.0.0 and using the Project reactor as reactive stream. The Global exception handler method never get called.
public class GlobalException extends RuntimeException{
public GlobalException(Throwable throwable){super(throwable);}
}
#Produces
#Singleton
#Requires(classes = {GlobalException.class, ExceptionHandler.class})
public class GlobalExceptionHandler implements ExceptionHandler<GlobalException, HttpResponse> {
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#Override
public HttpResponse handle(HttpRequest request, GlobalException exception) {
LOG.error(exception.getLocalizedMessage());
LOG.error(exception.getCause().getMessage());
Arrays.stream(exception.getStackTrace()).forEach(item -> LOG.error(item.toString()));
return HttpResponse.serverError(exception.getLocalizedMessage());
}
}
On exception with below code, the handler method never get called
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager.find(searchCriteria).onErrorMap(throwable -> {
return new GlobalException(throwable);
});
}
I had this code Global exception handling in micronaut Java from rxjava 3 which was working fine, however, now with project reactor it is not working
If you only intend to produce a single element it should be annotated with #SingleResult Or use Mono
#Override
#SingleResult
public Flux<List<FindProductCommand>> freeTextSearch(String text) {
LOG.info("Controller --> Finding all the products");
return iProductManager.findFreeText(text)
.onErrorMap(throwable -> {
throw new GlobalException(throwable);
});
}
Try this one:
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager
.find(searchCriteria)
.onErrorResume(e -> Mono.error(new GlobalException("My exception", e));
}
One reason for this might be that in your code, looking at this bit:
#Override
public Flux<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
LOG.info("Controller --> Finding all the products");
return iProductManager.find(searchCriteria).onErrorMap(throwable -> {
return new GlobalException(throwable);
});
}
If the code iProductManager.find(searchCriteria) is calling a rest endpoint and getting a 404 not found, you will not get an error in the error handler. Rather the result is that you will get the equivalent of an Optional.empty().
If you want to force en error, you could change it to something like:
iProductManager.find(searchCriteria).switchIfEmpty(Mono.error(...))

Multiple annotation aspects interacting with reactor core chain cause IllegalStateException in method invocation

I am working on a Spring Web reactive project and I am currently implementing the collection of different metrics for requests. It turns out that having more than one #Around annotations interacting with the Mono return object of my rest-controller method cause Spring to throw an IllegalStateException.
I am using Java 11 with spring boot 2.1.5-RELEASE
I stepped through the spring code and tried to find out what exactly is going wrong, and it seems to me that when calling proceed() on the ProceedingJoinPoint the spring aop logic confuses arguments of different annotations. Specifically the getUserAttribute call in AbstractApsectJAdvice line 684 returns null and seems to be provided with a wrong argument. I also tried changing the order of the aspect methods without success.
The following method is the rest handler in question, the #TimedMono and #HostLanguageRequestCounted annotations both add into the reactor chain
#Counted("statistics_incoming_requests")
#TimedMono("statistics_incoming_requestExecutionTime")
#PostMapping("/translate/{id}/{field}")
#HostLanguageRequestCounted("statistics_incoming_hostLanguageRequestCount")
public Mono<TranslationResponseBody> getTranslation(#PathVariable String id,
#PathVariable String field,
#RequestBody TranslationRequestBody requestBody) {
return translateService.translate(id, requestBody.getTargetLanguage(), requestBody.getText(), field);
}
This is the method handling the TimedMono annotation:
#Around(value = "#annotation(timed)")
#Order(2)
#SuppressWarnings("checkstyle:illegalThrows")
public Mono<?> timeExecution(ProceedingJoinPoint pjp, TimedMono timed) throws Throwable {
Long tStart = System.nanoTime();
Object m = pjp.proceed();
long tEnd = System.nanoTime();
if (!(m instanceof Mono)) {
throw (new Exception("Method must return a mono object"));
}
Mono<?> mono = (Mono) m;
Consumer<Object> stopTimer = obj -> {
meterRegistry.timer(timed.value())
.record(tEnd - tStart, TimeUnit.NANOSECONDS);
};
return mono.doOnError(stopTimer).doOnNext(stopTimer);
}
And the method handling the HostLanguageRequestCounted annotation:
#Around(value = "#annotation(hostLanguageRequestCounted)")
#Order(1)
public Object gatherTenantMetrics(ProceedingJoinPoint pjp,
HostLanguageRequestCounted hostLanguageRequestCounted) throws Throwable {
Optional<TranslationRequestBody> body = HostLanguageRequestCountedMetricAspect
.getArgumentOfType(TranslationRequestBody.class, pjp);
return Mono.subscriberContext()
.flatMap(ctx -> {
log.info(ctx.toString());
return Mono.just(ctx.get("host"));
}).flatMap(host -> {
if (host != null && body.isPresent() && body.get().getTargetLanguage() != null) {
String metricKey = hostLanguageRequestCounted.value();
metricKey += "_" + host.toString().toLowerCase();
metricKey += "_" + body.get().getTargetLanguage().toLowerCase();
meterRegistry.counter(metricKey).increment();
}
try {
return (Mono<?>) pjp.proceed();
} catch (Throwable t) {
return Mono.error(t);
}
});
}
private static <T> Optional<T> getArgumentOfType(Class<T> clazz, ProceedingJoinPoint pjp) {
return Arrays.stream(pjp.getArgs())
.filter((arg) -> clazz.isAssignableFrom(arg.getClass()))
.map((obj) -> (T) obj).findFirst();
}
in which the exception is thrown (on return (Mono) pjp.proceed();)
The exception thrown is the following:
java.lang.IllegalStateException: Required to bind 2 arguments, but only bound 1 (JoinPointMatch was NOT bound in invocation)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.argBinding(AbstractAspectJAdvice.java:605)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
The next entry in the stack trace is the line in my code.
Is this an actual bug in spring or am I doing something wrong ? When I remove one of the annotations, the other one will execute without problems.

Nested annotations method interception in Guice

I searched a lot but couldn't find anything much useful.
Problem:
I have created custom annotation like:
#MapExceptions(value = {
#MapException(sources = {IllegalArgumentException.class, RuntimeException.class}, destination = BadRequestException.class),
#MapException(sources = {RuntimeException.class}, destination = BadRequestException.class)
})
I am using Guice for DI.
Do I have to write two method interceptors? Actual work is getting done in #MapException
If yes, then how can I call #MapException interceptors invoke method from #MapExceptions interceptors invoke method? I dont want to duplicate code.
My #MapException interceptor looks like following
public class MapExceptionInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocation.proceed();
} catch (Exception actualException) {
Method method = invocation.getMethod();
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof MapException) {
MapException mapException = (MapException) annotation;
Class<? extends Throwable> destinationClass = mapException.destination();
Class<? extends Throwable>[] sourceClasses = mapException.sources();
for (Class sourceExceptionClass : sourceClasses) {
if (actualException.getClass().isInstance(sourceExceptionClass)) {
Constructor ctr = destinationClass.getConstructor(String.class);
throw (Throwable) ctr.newInstance(actualException.getMessage());
}
}
}
}
throw actualException;
}
}
}
I am using following Binding currently
bindInterceptor(Matchers.any(), Matchers.annotatedWith(MapException.class), new MapExceptionInterceptor());
Is this okay? Or I can improve?
Thank You !
So, the inner annotation is just a data bag.
To solve this I wrote interceptor for outer annotation (MapExceptions) which does all the work.

Making a "TransactionAction" inner class

I've got a Spring + Hibernate + MySQL web application, which is just a hello-world-test-area for now.
One of my Service classes implements this method:
public List<Offerta> tutte() {
List<Offerta> tutte = null;
TransactionStatus status = txm.getTransaction( new DefaultTransactionDefinition() );
try {
tutte = dao.getAll(Offerta.class);
txm.commit(status);
} catch (Exception e) {
e.printStackTrace();
txm.rollback(status);
}
return tutte;
}
'txm' is an injected PlatformTransactionManager.
What I want now, is to avoid duplicating the "wrapping" transaction code in all my service's methods!
I would like something like this:
someHelperTransactionClass.doThisInTransaction(new TransactionAction() {
List l = dao.queryForSomething();
});
But that's a inner class: how can I pass in and out data from it? I mean, how can I get the resulting "l" list from that TransactionAction? You could answer in a number of ways to this specific case, but what I need is a generic TransactionAction or a different solution which let me write the actual database code, without having to write each time the same boring code.
Please do not answer "Why don't you use #Transactional annotations or AOP tx:advice configuration?" because I CAN'T!
Why? I am on Google AppEngine, and that cool guys are not so cool: the disabled access to the javax.naming package, and something in those great ways to declarative transactions touches it. :-\
You can imitate basic AOP mechanism using Proxy objects. Such as http://www.devx.com/Java/Article/21463/1954
This is a mock. But I really doubt it plays well with Spring or GAE. PLease note that you need to use interfaces for Proxies.
interface Dao {
List<Foo> getAllFoo();
}
public class MyDao implements Dao {
public MyDao() {
}
public List<Foo> getAllFoo() {
//.. get list of foo from database. No need to use transactions
}
public static void main(String[] args) {
Dao dao = new MyDao();
InvocationHandler handler = new TransactionProxyHandler(dao);
Dao proxy = (Dao) Proxy.newProxyInstance(MyDao.class.getClassLoader(), MyDao.class.getInterfaces(), handler);
List<Foo> all = proxy.getAllFoo();
}
}
class TransactionProxyHandler implements InvocationHandler {
protected Object delegate;
PlatformTransactionManager txm = new PlatformTransactionManager();
public TransactionProxyHandler(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
TransactionStatus status = txm.getTransaction();
Object res = null;
try {
res = method.invoke(delegate, args);
txm.commit(status);
} catch (Exception e) {
e.printStackTrace();
txm.rollback(status);
}
return res;
}
}

Categories