'Add request query params in quarkus filter

I have a url like http://localhost:8080?scope=openid%20profile

I want to transfer it to http://localhost:8080?scope=openid&scope=profile so that the following endpoint could accept it.

// Scope.java
public enum Scope {
  OPENID,
  PROFILE;
  public static Scope fromString(String value) {
    return valueOf(value.toUpperCase());
  }
}

// AuthorizationEndpoint.java
  @GET
  @Path("authorize")
  public Response authorize(@Valid @NotEmpty @QueryParam("scope") Set<Scope> scope) {
    ...
  }

I tried to add a filter like below, but the map of request params is unmodifiable !

// Split.java
public @interface Split {
    String value();
}

// AuthorizationEndpoint.java
  @GET
  @Path("authorize")
  // add @Split(" ")
  public Response authorize(@Valid @NotEmpty @QueryParam("scope") @Split(" ") Set<Scope> scope)    
  {
    ...
  }

// SplitFilter.java
@Provider
class SplitFilter implements ContainerRequestFilter {
    
    private final ResourceInfo resourceInfo;

    SplitFilter(ResourceInfo resourceInfo) {
        this.resourceInfo = resourceInfo;
    }
    
    @Override
    public void filter(ContainerRequestContext requestContext) {
        for (var parameter: resourceInfo.getResourceMethod().getParameters()) {
            var queryParam = parameter.getAnnotation(QueryParam.class);
            if (queryParam == null) continue;
            var split = parameter.getAnnotation(Split.class);
            if (split == null) continue;
            var queryName = queryParam.value();

            // Note: queryParams is unmodifiable!!!
            var queryParams = requestContext.getUriInfo().getQueryParameters();
            var originQueryValues = queryParams.get(queryName);
            if (originQueryValues.size() != 1) {
                throw new IllegalStateException("Incorrect size of query values: " + originQueryValues + ", expect only 1.");
            }
            var splitQueryValue = originQueryValues.get(0).split(split.value());
            
            // Error: originQueryValues is unmodifiable!!!
            originQueryValues.clear(); 
            originQueryValues.addAll(Arrays.asList(splitQueryValue));   
        }
    }
}

So is there a proper way to modify the request params in filters or other inteceptors?

I also tried to make Set<Scope> a single class that recieves a String value as constructor param, but how to get the converter that converts a String value to a instance of Scope instead of calling Scope.fromString?



Solution 1:[1]

I think you can't use a filter for that, exactly because a filter is not allowed to change the params. You could use a param converter, roughly something like this:

@Provider
public class ScopeConverterProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
        if(rawType == Set.class) {
            for (Annotation annotation : annotations) {
                if (annotation.annotationType().equals(Split.class)) {
                    return (ParamConverter<T>) new ScopeConverter(((Split) annotation).value());
                }
            }
            return (ParamConverter<T>) new ScopeConverter(" ");// default if there's no @Split annotation
        }
        return null;
    }

}

public class ScopeConverter implements ParamConverter<Set<Scope>> {

    private final String splitCharacter;

    public ScopeConverter(String splitCharacter) {
        this.splitCharacter = splitCharacter;
    }

    @Override
    public Set<Scope> fromString(String value) {
        // strip the [] that the value gets automatically wrapped in,
        // because the query param is a set
        value = value.substring(1, value.length() - 1);
        String[] splits = value.split(splitCharacter);
        HashSet<Scope> scopes = new HashSet<>();
        for (String split : splits) {
            scopes.add(Scope.fromString(split));
        }
        return scopes;
    }

    @Override
    public String toString(Set<Scope> value) {
        return null;   // unused
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jan Martiška