0

I am trying to implement a PATCH endpoint with JAX-RS in a Liferay OSGi module. The GET, POST and PUT endpoints are working fine, but I am stuck with the PATCH endpoint. As I don't know any better, I am trying to use the example implementation of Daan Scheerens.

My (simplified) implementations so far, beginning with the controller:

@Path("/resources")
public class ResourceController {

    @PATCH
    @Path("/{resourceId}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    Response patchResource(@PathParam("resourceId") long resourceId, ObjectPatch objectPatch) {

        // Get the resource
        Resource resource = getResource(resourceId);

        // Apply the patch
        objectPatch.apply(resource);

        // Return the resource
        return Response.ok(resource).build();
    }
}

So I need an ObjectPatch interface that I did exactly like in Daan's example:

public interface ObjectPatch {

    <T> T apply(T target) throws ObjectPatchException;

}

Next step is to implement the MessageBodyReader:

@Provider
public class PartialJsonObjectPatchReader implements MessageBodyReader<ObjectPatch> {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {

        return ObjectPatch.class == type && MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
    }

    @Override
    public ObjectPatch readFrom(Class<ObjectPatch> type, Type genericType, Annotation[] annotations,
            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
            throws IOException {

        JsonNode patch = OBJECT_MAPPER.readTree(entityStream);

        return new PartialJsonObjectPatch(OBJECT_MAPPER, patch);
    }

}

The only difference to the example implementation is that I added the @Provider annotation. As far as I understood this registers the MessageBodyReader implementation automatically to the JAX-RS runtime, like it is described here and here. From the latter:

A class wishing to provide such a service implements the MessageBodyReader interface and may be annotated with @Provider for automatic discovery.

I just have the feeling that this automatic discovery does not happen.

The last important class is the implementation of the ObjectPatch interface:

public class PartialJsonObjectPatch implements ObjectPatch {

    private final ObjectMapper objectMapper;
    private final JsonNode patch;

    public PartialJsonObjectPatch(ObjectMapper objectMapper, JsonNode patch) {

        this.objectMapper = objectMapper;
        this.patch = patch;
    }

    @Override
    public <T> T apply(T target) throws ObjectPatchException {

        ObjectReader reader = objectMapper.readerForUpdating(target);

        try {
            return reader.readValue(patch);
        } catch (IOException e) {
            throw new ObjectPatchException(e);
        }
    }

}

If I now do a PATCH request to the endpoint, it gives me this error message:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.example.ObjectPatch (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

I kind of understand the error message as an interface can not be instantiated, but I do not understand why I get this message. Shouldn't it try to instantiate a PartialJsonObjectPatch instead of an ObjectPatch? If I change the parameter class of the patchResource() method to PartialJsonObjectPatch (which I shouldn't), I get this error message:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.example.PartialJsonObjectPatch (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

The error messages lead me to this, but adding a default constructor doesn't help.

What am I missing?

mirrom
  • 140
  • 1
  • 9

1 Answers1

0

The @Provider annotation is not sufficient, I had to add my MessageBodyReader implementation to the singletons in the Application class:

    @Override
    public Set<Object> getSingletons() {

        Set<Object> singletons = new HashSet<>();

        // All your other Providers, Readers, Writers, e.g:
        singletons.add(new JacksonJaxbJsonProvider());

        // My MessageBodyReader implementation
        singletons.add(new PartialJsonObjectPatchReader());

        return singletons;
    }
mirrom
  • 140
  • 1
  • 9