user991710 user991710 - 2 months ago 26
reST (reStructuredText) Question

OffsetDateTime yielding "No injection source found for a parameter of type public javax.ws.rs.core.response" in GET method

I have the following

GET
REST method:

import java.time.OffsetDateTime;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import com.product.rest.api.TransactionsApi;
import com.product.rest.model.Transaction;

@Path("/transactions")

@Api(description = "the transactions API")
@Consumes({ "application/json" })
@Produces({ "application/json" })
public class TransactionsApiImpl extends TransactionsApi {

@GET

@Consumes({ "application/json" })
@Produces({ "application/json" })
@ApiOperation(value = "", notes = "Get all transactions", response = Transaction.class, responseContainer = "List", tags = {})
@ApiResponses(
value = { @ApiResponse(code = 200, message = "OK", response = Transaction.class, responseContainer = "List"),
@ApiResponse(code = 400, message = "Bad Request", response = Transaction.class, responseContainer = "List"),
@ApiResponse(code = 404, message = "Not Found", response = Transaction.class, responseContainer = "List"),
@ApiResponse(code = 500, message = "Internal Server Error", response = Transaction.class, responseContainer = "List") })
@Override
public Response transactionsGet(
@HeaderParam("tok") String tok,
@QueryParam("param1") Integer param1,
@QueryParam("param2") String param2,
@QueryParam("param3") OffsetDateTime param3,
@QueryParam("param4") OffsetDateTime param4,
@QueryParam("param5") Integer param5,
@QueryParam("param6") Integer param6,
@QueryParam("param7") String param7) {
return Response.ok().entity("Success!").build();
}


The
TransactionsApi
is a generated implementation using Swagger Codegen, as is the
Transaction
model class. I have several other functions in this class, but whenever I leave the
GET /transactions
function uncommented, I receive the following error:

WARN [Thread-1] (ContextHandler.java:2175) - unavailable
org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.

[[FATAL] No injection source found for a parameter of type public javax.ws.rs.core.Response
com.product.rest.impl.v1.TransactionsApiImpl.transactionsGet(java.lang.String,java.lang.Integer,java.lang.String,java.time.OffsetDateTime,java.time.OffsetDateTime,java.lang.Integer,java.lang.Integer,java.lang.String) at index 3.; source='ResourceMethod{httpMethod=GET, consumedTypes=[application/json], producedTypes=[application/json], suspended=false, suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class com.product.rest.impl.v1.TransactionsApiImpl, handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor@7df78e88]}, definitionMethod=public javax.ws.rs.core.Response


All other similar questions I have found had to do with
MultiPart Data
and file uploading, whereas I am making a simple
GET
request. Other functions that also use the
javax.ws.rs.code.Response
class do not have this issue and the server starts normally.

I have noticed that the problem happens whenever the
OffsetDateTime
class is in the parameters (i.e.
param3
and
param4
), but I have been unable to find out why. Moreover,
OffsetDateTime
was chosen by Swagger Codegen and I am reluctant to change it seeing how I will have to change every derived file afterwards whenever I regenerate my sources.

Has anyone had this issue before with REST services and
OffsetDateTime
?

Answer

All other similar questions I have found had to do with MultiPart Data and file uploading

It's related. The error is a general error you get when Jersey can't validate the resource model. Part of the resource model is the method parameters. Jersey has a system for knowing which parameters it will be able to process and which ones it won't. In your case, it doesn't know how to process the OffsetDateTime.

There a a set of rules that you need to follow in order to able to use non basic types as @QueryParams:

  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 JAX-RS extension SPI 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.

So in this case of OffsetDateTime, going down the list; it's not a primitive; it doesn't have a String constructor; it doesn't have a static valueOf or fromString

So we basically the option left is to implement a ParamConverter/ParamConverterProvider for it. The basic set up looks like

@Provider
public class OffsetDateTimeProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> clazz, Type type, Annotation[] annotations) {
        if (clazz.getName().equals(OffsetDateTime.class.getName())) {

            return new ParamConverter<T>() {

                @SuppressWarnings("unchecked")
                @Override
                public T fromString(String value) {
                    OffsetDateTime time = ...
                    return (T) time;
                }

                @Override
                public String toString(T time) {
                    return ...;
                }
            };
        }
        return null;
    }
}

Basically, Jersey will pass you the String value of the query parameter, and it's your job to to create it and return it.

Then just register the OffsetDateTimeProvider with the application. If you're using package scanning, it should be picked up and registered automatically from the @Provider annotation.

I don't use Swagger, so I don't know if they already provide something like this already implemented, but it seems odd that they would generate this for you, and not have a way to make it work. I know Jersey 3 will have Java 8 support out the box, but who know when the heck that's gonna be released.