How to automate retrieval of DTOs from Sitebircks service

623 views Asked by At

Is there any way that automated DTO retrieval like this can be achieved but with sitebricks services.

I'm searching for solutions where this:

@Post
public Reply<?> post(Request request) {

  DTO dto = request.read(DTO.class).as(Json.class);
  //some dto operations

  .....
  return Reply.saying().ok();
}

could be transformed to something like this:

@Post
public Reply<?> post(@TransportedBy(Json.class) DTO dto) {

  //some dto operations

  .....
  return Reply.saying().ok();
}

where @TransoportedBy is custom annotation.

With a technique like this testing will be much easier because I wont need to mock the Request and expect its invocations.


..........Update..........


At this stage I've managed to implement something that is used like:

@Post
@TransportedBy(Json.class)//any transport can be used
public Reply<?> post(Request request, @Named("DTO") SomeDto dto) {

  //dto must be annotated with @Named("DTO")

  //some dto operations

  .....
  return Reply.saying().ok();
}

My implementation is like this, but I'm not satisfied with it.

The annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@BindingAnnotation
public @interface TransportedBy {
  Class value();
}

Method interceptor that listens for post methods

public class DtoTransportInterceptor implements MethodInterceptor{


  @Override
  public Object invoke(MethodInvocation methodInvocation) throws Throwable {

    Object[] arguments = methodInvocation.getArguments();
    Method method = methodInvocation.getMethod();
    Annotation[] declaredAnnotations = method.getDeclaredAnnotations();

    //retrieve the request
    Request request = null;
    for(Object argument : arguments){
      if(argument instanceof Request){
        request = (Request) argument;
        break;
      }
    }

    if (request == null) {
      return methodInvocation.proceed();
    }

    //find transport type
    Class transport = null;
    for(Annotation annotation : declaredAnnotations){
      if(annotation instanceof TransportedBy){
        Method m = annotation.getClass().getMethod("value");

        transport = (Class) m.invoke(annotation);
        break;
      }
    }

    if (transport == null){
      return methodInvocation.proceed();
    }

    //get the class type of the dto
    Class dtoClass = null;
    Annotation[][] parametersAnnotations = method.getParameterAnnotations();//all annotations for all parameters
    int dtoParameterId = 0;
    int parameterId = -1;
    for (Annotation[] paramAnnotations : parametersAnnotations){//iterate all parameters and return all annotations for the parameter
      parameterId++;
      for(Annotation annotation : paramAnnotations){//iterate all annotations for a single parameter
        if(annotation instanceof Named){
          Method m = annotation.getClass().getMethod("value");

          String namedValue = (String) m.invoke(annotation);

          if("DTO".equals(namedValue)){
            dtoClass = method.getParameterTypes()[parameterId];
            dtoParameterId = parameterId;
          }
        }
      }
    }

    if(dtoClass == null){
      return methodInvocation.proceed();
    }

    //if the dto is null initialize it and call the method again with the initialized dto, if the dto is not null proceed the method
    if (arguments[dtoParameterId] == null) {
      return method.invoke(methodInvocation.getThis(), request, request.read(dtoClass).as(transport));
    } else {
      return methodInvocation.proceed();
    }
  }
}

Bind the interceptor to listen for post methods in a guice module like this

DtoTransportInterceptor dtoTransportInterceptor = new DtoTransportInterceptor();

bindInterceptor(any(), annotatedWith(Post.class), dtoTransportInterceptor);

Since guice method interceptors doesn't support injections the request object still needs to be passed to the post method and retrieved with reflection inside the interceptor.

This solution is not pleasing because we still have to mock the request in the tests and pass it to the post method. At least we don't have to expect the invocation of "request.read(....)" in every test that retrieves the dto.

I'm open for other solutions.

0

There are 0 answers