Quantcast
Channel: unitstep.net » development
Viewing all articles
Browse latest Browse all 10

JAX-RS/Jersey needs an @Required annotation for parameters

0
0

I’ve been using Jersey as a JAX-RS implementation for a little while now, and one thing that it could benefit from is the addition of an @Required annotation for resource method parameters. Right now, when parameters are not provided by the client/request, they are simply set to null, creating the need for duplicated null-checking in resource methods. An @Required annotation would solve this issue and reduce code duplication.

This isn’t so much of an issue for @PathParam parameters, (since you won’t even get to the proper resource method without a matching URI) but it does affect @HeaderParam and @QueryParam (among others) since they aren’t needed for Jersey to determine which resource method to invoke. By that definition, they are implicitly optional. There should be a way to make them required.

The behaviour of such a required annotation might be as follows:

  • If the request does not have the parameter, then by default a Response with Status.BAD_REQUEST (HTTP 400) would be returned to the client.
  • Some way of customizing the HTTP response code and message should also be provided.

Right now, there’s not really an elegant way to make something like a @HeaderParam required. Here are some solutions I’ve tried.

Attempt #1: Parameter classes

Parameter classes can be useful for transforming the single input of a parameter into a single output, and also for verifying that the input parameter value is valid. This can be useful for ensuring that an input parameter can be converted into a specific object, or that it matches a specific format.

As an example, consider the CsvListParam class. This class takes a comma-separated list as a parameter, and returns a List<String> comprising each entry in the list. It does an additional check to ensure that the input is not blank, according to StringUtils.isBlank(). If it is blank, a 400 Bad Request response is returned to the client via the WebApplicationException thrown.

Note: The following examples use the AbstractParam class from Coda Hale’s article.

public class CsvListParam extends AbstractParam<List<String>> {

  public CsvListParam(String param) throws WebApplicationException {
    super(param);
  }

  @Override
  protected List<String> parse(final String suppliedStringValue) throws Throwable {
    if (StringUtils.isBlank(suppliedStringValue)) {
      throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).build());
    }
    return Arrays.asList(StringUtils.split(suppliedStringValue, ","));
  }
}

@GET
@Path("test")
public Response test(@HeaderParam("X-TEST") final CsvListParam param) {
  final String output;
  if (null != param) {
    output = param.getValue().toString();
  } else {
    output = "param was null";
  }
  return Response.ok(output).build();
}

However, if the parameter is not present at all – for example, if this was obtained via the @HeaderParam annotation and the header was not present – then the code in the parameter class will not even be invoked by Jersey. Instead, the value will simply be null. If we require this parameter, it results in nasty if-else null-checking code in each of our resource methods that requires the parameter.

We need something that gets rid of the necessary null-checking in each resource method.

Attempt #2: Injection Providers

Coda Hale provides another brilliant example how to use Injection Providers in Jersey. Basically, with Injection Providers, you can do everything that you could do with Parameter classes, and more.

While Parameter classes are useful only for single-input to single-output mapping, Injection Providers can map multiple inputs to single or multiple outputs. This could allow you to take multiple values in the HTTP request and use them populate a single Bean, or use them to form some more complex single value. It also allows you to do validation, as throwing a WebApplicationException from an Injection Provider will cause the contained Response or status to be properly returned to the client.

So, we can achieve a similar effect using an Injection Provider. A first attempt at resolving the issue yields the CsvListProvider class:

@Provider
public class CsvListProvider extends AbstractInjectableProvider<CsvListParam> {
  public CsvListProvider() {
    super(CsvListParam.class);
  }

  @Override
  public CsvListParam getValue(final HttpContext httpContext) {
    final String suppliedStringValue = httpContext.getRequest().getHeaderValue("X-TEST");
    return new CsvListParam(suppliedStringValue);
  }
}

@GET
@Path("test")
public Response test(@Context final CsvListParam param) {
  final String output = param.getValue().toString();
  return Response.ok(output).build();
}

However, while this works as expected, it has the unfortunate side effect that the source of the parameter (an HTTP header of “X-TEST”) has to be specified in the Provider class rather than on the annotation. This isn’t ideal since we have to create a new Injection Provider class for each HTTP header we want to make required.

Further attempts

I have been trying to figure out a solution to this. One possible way might be to change the AbstractInjectableProvider to the following declaration:

public abstract class AbstractInjectableProvider<E, A extends Annotation> extends AbstractHttpContextInjectable<E> implements InjectableProvider<A, Type>

We could then define a custom annotation type to take the place of A instead of always using @Context. However, this doesn’t work, as we have no way of then obtaining any of the annotation’s values in the concrete Provider class. A solution like this would require changes in the core of Jersey to make it work, thus reducing the solution essentially the same as having an @Required annotation as proposed above.

Conclusion

It seems like we need a proper solution to this via a change in Jersey. Evidently, others have come to the same conclusion, as there are at least two issues open for Jersey related to this.


Copyright © 2013 unitstep.net. This Feed is for personal non-commercial use only. If you are not reading this material in your news aggregator, the site you are looking at is guilty of copyright infringement. Please contact webmaster@unitstep.net for more information.
Plugin by Taragana

Viewing all articles
Browse latest Browse all 10

Latest Images

Trending Articles





Latest Images