3

I have clients passing in IDs like this: /v1/path?id=1,2,3

What I have and want

I have a resource class for Dropwizard/Jersey. I'd like to show up the query-parameter id=1,2,3 as a List parameter in my resource's GET method

// Resource class

public List<Something> getFilteredList(@QueryParam("id") List<String> ids) {
  // filter the List<Something> based on a list of ids
}

Right now, the ids list contains 1 string which is "1,2,3".

What I tried

I tried a filter but the query parameters given by Jersey's ContainerRequestContext.getUriInfo().getQueryParameters() is immutable.

Questions

I would like to apply a filter and change any comma separated query parameters into multi-valued parameters so that the resource method gets a list instead.

  1. Is there a way to change the existing query parameters using a Jersey filter?
  2. What's a good way to solve this problem?
hc_dev
  • 8,389
  • 1
  • 26
  • 38
Devu
  • 381
  • 3
  • 15
  • Would you like to modify the query-parameters, i.e. add to or remove from the `List id` within the method? Or what is the problem with an immutable? – hc_dev Nov 23 '22 at 21:32

2 Answers2

1

The best way I can think of is to just create a wrapper class for the list. This makes it easier to take advantage of the specified functionality of Jersey. You can see what I mean at Passing custom type query parameter.

For example

public class IdFilter {
    private List<String> ids = new ArrayList<>();
    public List<String> getIds() { return ids; }

    public static IdFilter valueOf(String param) {
        IdFilter filter = new IdFilter();
        for (String id: param.split(",") {
            filter.getIds().add(id);
        }
    }
}

getFilteredList(@QueryParam("id") IdFilter ids) {

We don't need to do anything else. Just having the static valueOf is enough for Jersey to know how to parse the query string.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
0

3 ways to solve it:

  1. use the generic context-parameter UriInfo , which is not very expressive
  2. add an explicit custom type that can parse a comma-separated list
  3. stay with @QueryParam List<String> requiring a concatenated query like ?id=1&id=2&id=3 given as URI

I would prefer the second as most-expressive, like answered already by Paul. This way you can concisely pass a single CSV like ?id=1,2,3,3 and also use a Set to ensure unique ID values, e.g. resulting in only [1, 2, 3].

Generic context-param UriInfo

One way would be to use a generic parameter @Context UriInfo to get the list in the method's body:

public List<Something> getFilteredList( @Context UriInfo uriInfo ) {

  List<String> idList = uriInfo.getQueryParameters().get("id"); // before was @QueryParam("id")
  System.out.println("idList: " + idList);
  
  // filter a given list by ids
  var somethingFiltered = getSomethingList().stream()
    .filter(s -> idList.contains(s.getId()))
    .collect(toList());
  
  return Response.status(Status.OK).entity(somethingFiltered).build();
}

See the tutorial in Java Vogue(2015): QueryParam Annotation In Jersey -

Custom type with static valueOf(String) factory-method

The other way is to design a custom type which can be constructed using a String:

class IdSet {
  
  Set<String> values;

  // a factory method, can also be named valueOf
  public static IdSet fromString(String commaSeparated) {
    return new HashSet( Arrays.asList( commaSeparated.split(",") ) );
  }
}

public List<Something> getFilteredList(@QueryParam("id") IdSet ids) {
  System.out.println("ids (Set): " + ids.values);
  
  // filter a given list by ids
  var somethingFiltered = getSomethingList().stream()
    .filter(s -> ids.values.contains(s.getId()))
    .collect(toList());
  
  return Response.status(Status.OK).entity(somethingFiltered).build();
}

See Jersey's JavaDocs for @QueryParam:

The type T of the annotated parameter, field or property must either:

  1. Be a primitive type
  2. Have a constructor that accepts a single String argument
  3. Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String))
  4. Have a registered implementation of ParamConverterProvider that returns a ParamConverter instance capable of a "from string" conversion for the type.
  5. Be List<T>, Set<T> or SortedSet<T>, where T satisfies 2, 3 or 4 above. The resulting collection is read-only.

Use a collection interface with multiple key-value pairs

When the calling client uses following URI pattern: /something?id=1&id=2&id=3 then JAX-RS can deserialize them to a single parameter of List<String> id having given multiple elements:

public List<Something> getFilteredList(@QueryParam("id") List<String> ids) {
  System.out.println("ids : "+ids);
  
  // filter a given list by ids
  var somethingFiltered = getSomethingList().stream()
    .filter(s -> ids.contains(s.getId()))
    .collect(toList());
  
  return Response.status(Status.OK).entity(somethingFiltered).build();
}

See Mkyong: JAX-RS @QueryParam example where explained the multiple occurrences of orderBy in the GET query:

@QueryParam will convert the query parameter “orderBy=age&orderBy=name” into java.util.List automatically.

See also

hc_dev
  • 8,389
  • 1
  • 26
  • 38