Search code examples
javajax-rsresteasyvert.xmdc

JAX-RS DynamicFeature implementation not called from VertxResteasyDeployment


I'm trying to Log unique request id for each http request using MDC to my resource for debugging and trace a particular request. For same I've created one custom annotation @TagRequestID. Below is the for logging request id. But what I'm not able to achieve the request is not going via DynamicFilter implementation what I think there should be some method or way in VertxResteasyDeployment class which should help to resolve this.

Basically the request is not going via filter even I tried with setProviders method of VertxResteasyDeployment class. Can someone please guide what I'm missing here ? I believe there should be some config which request to pass request via DynamicFeature implementation where we inject RequestIdConfig bean. (Assume I've RequestIdConfig bean created).

@TagRequestID code :

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TagRequestID {
}

custom class RequestIdConfig :

import lombok.Data;
import javax.validation.constraints.NotNull;

@Data
public class RequestIdConfig {
 @NotNull
 private String resourcePackage;

 @NotNull
 private String requestIdHeaderKey;

 @NotNull
 private Boolean requestIdMandatoryFlag;
}

custom class RequestIdFeature :

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;

@Slf4j
@AllArgsConstructor
@Provider
public class RequestIdFeature
 implements DynamicFeature {

 RequestIdConfig requestIdConfig;

 @Override
 public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) {
 log.info("testing now===");

 final Class<?> resourceClass = resourceInfo.getResourceClass();
 //Check if the current resource is to validated
 if (resourceClass.getPackage().getName().startsWith(requestIdConfig.getResourcePackage())) {
 //Check if the Validation annotation is present
 if (resourceInfo.getResourceMethod().getAnnotation(TagRequestID.class) != null) {
 log.info(resourceInfo.getResourceMethod() + " registered for clientID validation");
 featureContext.register(new RequestIdFilter(requestIdConfig, resourceInfo));
 }
 }
 }
}

custom class for filter :

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.MDC;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.UUID;

@Data
@AllArgsConstructor
@Slf4j
@Provider
public class RequestIdFilter
 implements ContainerRequestFilter {

 RequestIdConfig requestIdConfig;
 ResourceInfo resourceInfo;

 public static final String REQUEST_ID = "request-Id";

 @Override
 public void filter(ContainerRequestContext containerRequestContext) throws IOException {

 log.info("ClientIdValidation filter method invoked");

 Method resourceMethod = resourceInfo.getResourceMethod();

 // Validate Clients
 validatePermissions(containerRequestContext);
 }

 private void validatePermissions(final ContainerRequestContext containerRequestContext) {
 String requestId = containerRequestContext.getHeaderString(requestIdConfig.getRequestIdHeaderKey());

 //Make sure the Header key is present if mandatory flag is true
 if (requestIdConfig.getRequestIdMandatoryFlag() && StringUtils.isAnyEmpty(requestId)) {
 throw new WebApplicationException(requestIdConfig.getRequestIdHeaderKey() + " can't be null", Response.Status.UNAUTHORIZED);
 }

 //If no request ID present, generate a UUID
 if (StringUtils.isAnyEmpty(requestId)) {
 requestId = UUID.randomUUID()
 .toString();
 }

 containerRequestContext.setProperty(REQUEST_ID, requestId);
 MDC.put(REQUEST_ID, requestId);
 }
}

Resource or Controller code :

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import io.vertx.core.Vertx;
import io.vertx.core.WorkerExecutor; 

public class Processor {
@POST
@TagRequestID
@Path("/update_record")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON })
public void updateEvent(String data) throws Exception{
//do something here
}

Server code from where we run this:

import mypackage.Processor;
import io.vertx.core.AbstractVerticle;
import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
import org.springframework.context.ApplicationContext;


public class VertxServer extends AbstractVerticle {
    VertxServer(final ApplicationContext context) {
    }

    @Override
    public void start() throws Exception {
        VertxResteasyDeployment deployment = new VertxResteasyDeployment();
        deployment.start();
        deployment.getRegistry().addPerInstanceResource(Processors.class);
        vertx.createHttpServer()
                .requestHandler(new VertxRequestHandler(vertx, deployment))
                .listen(8080);
    }
}

Once server is up and running then just hit two request simultaneously on the above controller . i.e :

curl -X POST \ http://localhost:8080/v1/update_record \ -H 'Cache-Control: no-cache' \ -H 'Content-Type: application/json' \
-H 'Postman-Token: c9494189-4ac9-9f6c-44f6-216186c74431' \ -d '{"id":"123"}'


Solution

  • In VertxServer.java register your dynamicFeature instance in providerFactory before you register your resources. This dynamicFeatures and filters registered in providerFactory will be used while registering resources.

    Your code will go like this:

    VertxResteasyDeployment deployment = new VertxResteasyDeployment();
    deployment.start();
    deployment.getProviderFactory().register(new RequestIdFeature(getRequiredBean());
    deployment.getRegistry().addPerInstanceResource(Processors.class);
    vertx.createHttpServer()
                .requestHandler(new VertxRequestHandler(vertx, deployment))
                .listen(8080);