rjjdv
rjjdv

Reputation: 415

My custom interceptor is not being executed in Quarkus

I am writing a small app in Quarkus. I am mostly using the quarkus-hibernate-reactive-rest-data-panache extension. One thing is that it doesn't support a partial search for query params. So for example, when searching for the name of a user, it needs the full name instead just the first couple of characters. I'd like to support that.

Now I've written below annotation:

@InterceptorBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PartialSearch {
    String value() default "name";
}

And implemented it like this:

@Priority(1)
@Interceptor
@PartialSearch
public class PartialSearchInterceptor {

    @Context
    UriInfo uriInfo;

    @AroundInvoke
    public Object intercept(InvocationContext context) throws Exception {
        Log.info("Inside of interceptor");

        try {
            Response originalResponse = (Response) context.proceed();

            PartialSearch partialSearch = context.getMethod().getAnnotation(PartialSearch.class);
            String searchProperty = partialSearch.value();
            String searchTerm = uriInfo.getQueryParameters().getFirst(searchProperty);

            if (originalResponse.getEntity() instanceof List<?> originalEntities) {
                List<Object> filteredEntities = new ArrayList<>();

                for (Object entity : originalEntities) {
                    if (matchesSearchCriteria(entity, searchProperty, searchTerm)) {
                        filteredEntities.add(entity);
                    }
                }

                return Response.fromResponse(originalResponse)
                        .entity(filteredEntities)
                        .build();
            }

            return originalResponse;
        } catch (SearchCriteriaException e) {
            Problem problem = new Problem(
                    "https://example.com/problem/invalid-property",
                    "INVALID_SEARCH_PROPERTY",
                    Response.Status.BAD_REQUEST.getStatusCode(),
                    e.getMessage(),
                    uriInfo.getAbsolutePath().toString());

            return Response.status(Response.Status.BAD_REQUEST).entity(problem).build();
        }

    }

    private boolean matchesSearchCriteria(Object entity, String propertyName, String searchTerm) throws SearchCriteriaException {
        try {
            Field field = entity.getClass().getDeclaredField(propertyName);
            field.setAccessible(true);
            Object value = field.get(entity);

            Log.debug(value);
            Log.debug(searchTerm);

            if (value instanceof String) {
                return ((String) value).contains(searchTerm);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new SearchCriteriaException("Error accessing property '" + propertyName + "': " + e.getMessage());
        }
        return false;
    }

    private static class SearchCriteriaException extends Exception {
        public SearchCriteriaException(String message) {
            super(message);
        }
    }
}

And this is the method I am using it one. It's in an interface, because that's how to extension works.

@ResourceProperties(path = "/users")
public interface UserResource extends PanacheEntityResource<User, UUID> {

    @PartialSearch("email")
    Uni<List<User>> list(Page page, Sort sort);
}

The idea behind the annotation is that it takes the result of the API method, and then does some filtering on the provided property (defaults to 'name').

I added some logging, but when I hit the endpoint I don't see the logs in the Quarkus console and I am getting either an empty response (so the default behaviour of the extension) or a response when I search for the full name.

Does anyone know why the annotation might not be getting executed?

Upvotes: 0

Views: 433

Answers (1)

fxrobin
fxrobin

Reputation: 652

You need to add @NonBinding in front of the attribute value of your PartialSearch annotation interface.

In your actual case, the interceptor is only involved when "name" == "default" which is not the case in your UserResource class which uses @PartialSearch("email").

The solution :

@InterceptorBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PartialSearch {
    @NonBinding String value() default "name";
}

Now your interceptor will be invoked regardless of the content of the attribute.

If you wonder why, this default mechanism is very useful to have different interceptors based on annotation values without having to deal with "if or switch statements" in your "@AroundInvoke" method.

See : https://jakarta.ee/specifications/cdi/3.0/apidocs/?jakarta/enterprise/util/Nonbinding.html

Upvotes: 0

Related Questions