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?