How to do JAX-RS partial update with bean validation - java

It may seem crazy but I'm having trouble with JAX-RS partial JSON requests. For example suppose we have an entity:
public class Data {
private String a;
private String b;
private Integer c;
/* this is flat and large object with many fields */
}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
#Path("/data")
public Response updateData(Data d) {
/* update database */
}
Now I want to let user update some fields. So user sends request like this:
{ "a": "data of field a" }
This means only update field "a". When user wants to set field "a" to null he will send { "a": null }. The problem is on server side I can't decide Whether client wants to set field "a" to null or there is no field "a" in the request thus I should not touch field "a"(In both cases "a" will be null). One solution is to read Map<String, Object> instead of Data. In this way I can differentiate these two cases, but I will loose bean validation framework plus I should do lots of type casting(Object to String/Integer/List<Integer>). Is there any concise way to handle such partial update requests? I'm using Jersey 2.9 with Jackson 2.3 on Jetty.

Related

Spring JPA - RESTful partial update and validation for entity

I've a simple RESTful API based on Spring MVC using a JPA connected MySQL database. Until now this API supports complete updates of an entity only. This means all fields must be provided inside of the request body.
#ResponseBody
#PutMapping(value = "{id}")
public ResponseEntity<?> update(#Valid #RequestBody Article newArticle, #PathVariable("id") long id) {
return service.updateById(id, newArticle);
}
The real problem here is the validation, how could I validate only provided fields while still require all fields during creation?
#Entity
public class Article {
#NotEmpty #Size(max = 100) String title;
#NotEmpty #Size(max = 500) String content;
// Getters and Setters
}
Example for a partial update request body {"content": "Just a test"} instead of {"title": "Title", "content": "Just a test"}.
The actual partial update is done by checking if the given field is not null:
if(newArticle.getTitle() != null) article.setTitle(newArticle.getTitle());
But the validation of course wont work! I've to deactivate the validation for the update method to run the RESTful service. I've essentially two questions:
How can I validate only a "existing" subset of properties in the
update method while still require all fields during creation?
Is there a more elegant way for update partially then checking for
null?
The complexity of partial updates and Spring JPA is that you may send half of the fields populated, and even that you will need to pull the entire entity from the data base, then just "merge" both entity and the pojo, because otherwise you will risk your data by sending null values to the database.
But merging itself is kind of tricky, because you need to operate over each field and take the decision of either send the new value to the data base or just keep the current one. And as you add fields, the validation needs to be updated, and tests get more complex. In one single statement: it doesn't scale. The idea is to always write code which is open for extension and closed for modifications. If you add more fields, then the validation block ideally doesn't need to change.
The way you deal with this in a REST model, is by operating over the entire entity each time you need. Let's say you have users, then you first pull a user:
GET /user/100
Then you have in your web page the entire fields of user id=100. Then you change its last name. You propagate the change calling the same resource URL with PUT verb:
PUT /user/100
And you send all the fields, or rather the "same entity" back with a new lastname. And you forget about validation, the validation will just work as a black box. If you add more fields, you add more #NotNull or whatever validation you need. Of course there may be situations where you need to actually write blocks of code for validation. Even in this case the validation doesn't get affected, as you will have a main for-loop for your validation, and each field will have its own validator. If you add fields, you add validators, but the main validation block remains untouchable.

Single POJO for different REST operations with validation

I'm designing a REST service and am running into the issue that for a given object, I have multiple "states".
The object as it arrives on the initial POST operation.
The Object I store in our DB
The Object I return on a GET
The Object I expect on a PATCH
e.g.
class MyObject {
// Unwanted on POST
// Required on PATCH
// Included on GET
#JsonProperty("id")
private UUID id;
// Everywhere
#NonNull
#JsonProperty("name")
private String name;
// Field I need for internal processing but don't want included in REST.
private AuditTrail stuff;
#JsonCreator
#Builder
public MyObject(...) { ... }
}
...
#Get
public ResponseEntity myFunction(HttpServletRequest request,
#RequestBody #Valid MyObject requestBody) {
...
}
The issue I am running into is that on POST, when the id is omitted, the deserialization fails. I got around it using #JsonIgnoreProperties(), but now on PATCH, where I do want the id present, things work if it is omitted.
Another alternative we toyed with was to have two objects. The first one with the common fields for POST and the other extending from it with the rest, but it feel messy, especially as we deal with objects more complex than the simple example.
It's not actually a problem since I validate and sanitize inputs anyway, but I was wondering if there is a clean way in Jackson to solve this issue.
If you are planning a rest service then you don't need the id in the body anyway. The id will come from the url as a pathvariable:
POST myobjects
GET myobjects/{id}
PATCH myobjects/{id}

Partial fields update REST API

There is this MongoBean: SuperBean
class SuperBean extends MongoBaseBean{
private String id;
private String title;
private String parent;
//And getters, setters
}
Need is to write an update API, which is capable of performing partial attributes update. Common approach seen across the web as well as heard from my peers is to check the fields in the request for Null and update if not null. But what if the update request is for the value to be updated to Null??
After few discussions, we came up with three approaches:
Set default value for the field in the bean. Hence instead of non-null parent field, if it does not have $ in the request, this will be considered for update.
class SuperBean extends MongoBaseBean{
private String id;
private String title;
private String parent = "$";
//And getters, setters
}
Let the update API Implementation accept a Map. The actual bean is fetched and all the fields that are present in the request map will be updated.
#Post
public SuperBean updatePartial(Map<String,Object> dataObject) {}
Let the update API accept DTO, that contains 2 maps. One to contain old values, other for new values. This could be advantageous in scenarios, where the update has to happen only if the database contains the values as sent in oldDataObj. But this increases the payload size.
class SuperBeanUpdateDTO {
private Map<String, Object> oldDataObj;
private Map<String, Object> newDataObject;
//getters, setters
}
#Post
public SuperBean updatePartial(SuperBeanUpdateDTO updateDTO) {}
What factors should be considered to chose one of these approaches? Is there another better way to approach this problem?
In my projects, we usually choose the way that similar with your second way. but not exactly the same.
for example, in your client side, you have a page or a view to modify your profile info, includes name, birthday, gender, although you just modify the name value, when you click save button, it still will send the data to server includes birthday and gender with name field, but just keep its value as old. and the server API will directly update these three values in database, won't check whether its value changed or not.
if you have another page or view to modify other parts of the profile, likes password, it need add a new method in client and a new API in server. the API URL likes PATCH /reset_password, and the sent data should include old_password and new_password field.
PS:
1. we use PUT or PATCH to update a resource, not POST, POST is used to create a new resource.
2. when you update a resource, in the above example, the API likes PATCH /profiles/:id (other's profile) or PATCH /profile (yourself profile), so the sent data doesn't need id field anymore, it includes in your API URL.

Spring Rest Dropping Data When Returning

I have a simple Spring Rest service setup to handle some JSON objects I have in a DB. I have two applications; one looking to hold that data, and the other is looking to request/store that data locally to its DB. The object used is in a shared Jar that both applications have access to, so they know the structure, expectations, etc.
When I run a request for the data I can see it coming in, but some of the data has been nulled out in the JSON. I have setup breakpoints at the last return point for the source system, and when the data is there (pulled from DB) and ready to return it is intact. Once it gets across the wire in any form via Postman or my 2nd App, some of the fields have been blanked out.
EDIT: Some code posted with specific names simplified for ease of reading
//------------------The Data "Source"------------------------------
//The source service that serves the content
#ResponseBody
#RequestMapping(value = { "/json/listObjectA" }, method = RequestMethod.GET)
public List<ObjectA> listAllObjectA() {
List<ObjectA> allObjectA = ObjectA
.getAll(thisApplicationContext);
return allObjectA; //a breakpoint here shows the data as expected
//the data in the DB is correct in source, confirmed.
}
//------------------The Requesting "Target"-----------------------------
//trying to get that data, setting up a baseURL for the service works
//when using this block of code with debuggers on, it reaches the source above.
RestTemplate rest = new RestTemplate();
ResponseEntity<ObjectA[]> sfResponse = rest.getForEntity(
coreBaseLocation+"json/listObjectA",
ObjectA[].class);
ObjectA[] sfList = sfResponse.getBody();
// right here debugging, the list has the correct number, but some of the data is missing
for (ObjectA sf: sfList) {
sf.setId(null); //clear the local id, a uuid is persisting
sf.save(thisApplicationContext);
}
//------------------The data class------------------------------
#Entity
public class ObjectA{
...
protected String ObjectBUuid;
protected String ObjectCUuid;
...
// basic POJO getters/setters/constructor etc. nothing fancy
Also for reference, ObjectA is a simple "Join" object between an ObjectB and ObjectC. A simple join table doesn't work since I am storing a lot more meta data about the association that isn't super relevant to this but needed for business reasons.
//Here is a copy of the "response" from Postman hitting that json url.
//again, in debug before this is sent back the data looks correct with the Uuids populated (like in the DB)
[
{
"id": 1, //local id of the ObjectA
"uuid": "1464d8dc-0281-4850-a51d-a375f7a2fd04", //ObjectA's uuid
... metadata stuff ...
// end of obj., no reference to the ObjectB/C uuids at all in the returned obj.
}, // more data here matching the number I expect from the DB and the source debug breakpoint, just with the reference uuid missing.

How to control Jersey serialization using query params

I am looking for a dynamic way to control the response object coming back from a request using query params.
I am using Jersey 2.x and Hibernate 4 for managing entities along with some Spring sprinkles for security etc.The problem is that Jersey is not serializing the attached entity but only the base entity. I am currently using the com.fasterxml.jackson.datatype.hibernate4. that gives me some of the flexiblity to handle how to load child and parent entities using JPA fetch=Eager etc. However I really want to make this dynamic.
I tried a simple dynamic loading by specifying a ?with=<someentity> to specify what entities to attach. When fetching the entity I use reflection to call the getter for someentity and it attaches the entity successfully but when sending the entity out it is not serializing the attached entity.
Here is a super simple example of what I am trying to do. This is really just a piece parted together but the idea is there. The problem is when I get the Campaign object back form the server it is not serializing the entities that are attached by calling loadEntity.
#Path("campaign")
public class CampaignResource {
#GET
#Path("{entity_id}")
public Campaign find(#PathParam("entity_id") final Long id, #QueryParam("with") final String with) {
T entity = repository.findOne(id);
load(entity, with);
return entity;
}
/**
* This is used to attach entities that are requested via the api.
*
* #param entity
* #param with
*/
#SuppressWarnings("unused")
protected void loadWithEntities(T entity, final String with) {
String[] withFields;
if (with.contains(",")) {
// Split the with clause into separate values
withFields = with.split(",");
} else {
// Single with clause
withFields = new String[] { with };
}
for (String field : withFields) {
final String getterMethodName = getMethodGetterForField(field);
Method method = null;
try {
method = entityClass.getMethod(getterMethodName);
if (method != null) {
logger.info("Loading entity " + getterMethodName);
// Some odd reason we have to assign the variable so that it
// is attached.
final Object attached = method.invoke(entity);
}
} catch (Exception e) {
logger.error("Unable to find method name %s ", getterMethodName, e);
}
}
}
}
Jersey has the Entity Data Filtering to handle this use case. Hopefully you are using a later version of Jersey, as Jackson is not supported until (somewhere between 2.14 :-) and 2.16. Too lazy to check when. I'm guessing you are using the jersey-media-json-jackson. You will know if you version is supported if it pulls in the jersey-entity-filtering dependency. You don't need to add anything else.
You only need to configure three thing:
Register the SelectableEntityFilteringFeature.
Configure the query parameter name.
.property(SelectableEntityFilteringFeature.QUERY_PARAM_NAME, "with")
There are different types of filtering features, but here is the section on query param filtering. There's not much information, because well, there's not much information to tell. All you really need to know is how to configure, and it work as you expect, i.e. ?with=prop1,prop2,prop3

Categories

Resources