JAX RS 2.0 Not Converting UUIDs - java

I have been given an OpenAPI yaml file and have used the maven openapi generator plugin to turn this into Java. This is being deployed to Weblogic 12.2.1.4 which should have Jersey 2.22 intalled.
This is largely working except for some UUID columns which are just coming through as null.
There is an API class
#Path("/jobtest")
public class JobTestAPI {
#POST
#Consumes({ "application/json" })
public Response jobTestNotification(#Valid #NotNull JobTestNotification jobTetNotification) {
return Response.ok().entity("OK").build();
}
}
and a data class
#JsonTypeName ("JobTestNotification")
public class JobTestNotification {
private #Valid UUID jobId;
private #Valid Date jobTimestamp;
private #Valid String jobDescription;
#JsonProperty("jobId")
#NotNull
public UUID getJobId() {
return jobId;
}
#JsonProperty("jobId")
public void setJobId(UUID jobId) {
this.jobId = jobId;
}
// Other getters / setters with same annotations
}
Sending
{
"jobId": "abcdef12-abcd-ef12-1234-abcdef123456",
"jobTimestamp": "2022-11-10T09:00:00.000Z",
"jobDescription": "Some description"
}
I am getting a bad request and adding in an ExceptionMapper I see it is complaining that the UUID is null. Indeed if I take out the #Valid annotation on the API method and print out the object I can see the other two values are there but the UUID field is null.
If I change the datatype of jobId to String it works.
My understanding is that as UUID implements a static fromString method there is no need for a custom converter (and indeed trying to implement one didn't seem to work).
Why are these fields not being mapped through (I have treble checked they are being passed with correct names and are definitely there)? Other than just changing the values to String what can I do to get this work

Related

Error when convert JSON into an Object javax.json.bind.JsonbException: Can't create instance of a class: class [Lclass;, No default constructor found

First of all what iam using.
Server: Wildfly 20.0.1
IntelliJ
Open JDK 14
Github Repo: https://github.com/FreshDoktor/MinecraftApi-Stackoverflow
The full log: https://pastebin.com/XGEYuuWG
Standalone: https://pastebin.com/9FYwFNvB
I already double checked if my class overwriting the default constructor its not the case.
Also the L in front of the class in the message is not an typo. Thats what is written in my console.
Iam writing an Service with JEE and try to convert an response from JSON to an Object. To get the response i used the client from javax.ws.rs.
Unfortunately I have no idea what the problem could be and hope to get my mistake explainded her.
Enclosed you will find the class and method which I think are important for the error. Also i uploaded my project to git so you can look into every file you need.
Thanks in advance.
#PostConstruct
public void contextInitialized() {
System.out.println("StartupListener.contextInitialized - Start");
Client client = ClientBuilder.newClient();
List<FullMinecraftVersionEntity> objects = Arrays.asList(client.target("https://launchermeta.mojang.com/mc/game/version_manifest.json") //
.request(MediaType.APPLICATION_JSON) //
.get(FullMinecraftVersionEntity[].class));
System.out.println(objects);
System.out.println("StartupListener.contextInitialized - End");
}
#Entity
#Table(name = "FULL_VERSION")
public class FullMinecraftVersionEntity {
#Id
#Column(name = "ID")
private String id;
#Column(name = "TYPE")
private String type;
#Column(name = "URL")
private String url;
#Column(name = "TIME")
private String time;
//Getter Setter
#Override
public String toString() {
}
#Override
public boolean equals(Object o) {
}
#Override
public int hashCode() {
return Objects.hash(getId(), getType(), getUrl(), getTime(), getReleaseTime());
}
}
I've got it, the object i want to deserialize and the json response did not match up.

Spring boot - class validation not working on REST API

I have the following REST API:
#ResponseBody
#PostMapping(path = "/configureSegment")
public ResponseEntity<ResultData> configureSegment(#RequestParam() String segment,
#RequestBody #Valid CloudSegmentConfig segmentConfig
) {
CloudSegmentConfig:
#JsonProperty(value="txConfig", required = true)
#NotNull(message="Please provide a valid txConfig")
TelemetryConfig telemetryConfig;
#JsonProperty(value="rxConfig")
ExternalSourcePpdkConfig externalSourcePpdkConfig = new ExternalSourcePpdkConfig(true);
TelemetryConfig:
public class TelemetryConfig {
static Gson gson = new Gson();
#JsonProperty(value="location", required = true)
#Valid
#NotNull(message="Please provide a valid location")
Location location;
#Valid
#JsonProperty(value="isEnabled", required = true)
#NotNull(message="Please provide a valid isEnabled")
Boolean isEnabled;
Location:
static public enum Location {
US("usa"),
EU("europe"),
CANADA("canada"),
ASIA("asia");
private String name;
private Location(String s) {
this.name = s;
}
private String getName() {
return this.name;
}
}
When I'm trying to send the following JSON:
{
"txConfig": {
"location": "asdsad"
}
}
The API return empty response 400 bad request, while I expect it to validate the location to be one of the ENUMs of the class. I also expect it to validate the isEnable parameter while it doesn't although I added all possible annotation to it..
Any idea?
Use #Valid annotation on TelemetryConfig telemetryConfig and no need to use #Valid on the field of TelemetryConfig class.
#Valid
TelemetryConfig telemetryConfig;
And for enum subset validation you can create a customer validator with annotation and use it.
A good doc about this Validating a Subset of an Enum

#valid annotation sends exception in response to rest service

I am using custom validation in my rest web services.
#PUT
#Path("/{accountId}")
#Consumes({MediaType.APPLICATION_JSON})
public Response update(
#NotNull #ValidUUID #PathParam("accountId") UUID accUUID,
#NotNull #Valid Account changedAcc) {
synchronized (LOCK_MANAGER.getLock(accUUID)) {
return accHelper.update(this.getCurrentUser(), accUUID, changedAcc);
}
}
here is a glimpse at my Account class
#Table(keyspace = "Randomss", name = "accounts")
public class Account {
#PartitionKey
#Column(name = "id")
#JsonIgnore
private UUID id;
#Column(name = "acc_type")
#NotNull
#ValidString
#JsonIgnore
private String accType;
Now I send JSON data to this web service to update account,
but if I send some wrong json data
(e.g acc_type is expected as string and I send numeric data)
then it throws an exception.
How do I get it to send an error message instead of throwing an exception
(specifically, I want to send the error message)?
You need to write a javax.ws.rs.ext.Provider that implements an javax.ws.rs.ext.ExceptionMapper.
For example a generic ValidationExceptionMapper might look like:
#Provider
public class ValidationExceptionMapper
implements ExceptionMapper<ValidationException> {
public Response toResponse(ValidationException e) {
return Response.status(BAD_REQUEST).build();
}
}
You can choose a more appropriate response to return.

How to distinguish between null and not provided values for partial updates in Spring Rest Controller

I'm trying to distinguish between null values and not provided values when partially updating an entity with PUT request method in Spring Rest Controller.
Consider the following entity, as an example:
#Entity
private class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/* let's assume the following attributes may be null */
private String firstName;
private String lastName;
/* getters and setters ... */
}
My Person repository (Spring Data):
#Repository
public interface PersonRepository extends CrudRepository<Person, Long> {
}
The DTO I use:
private class PersonDTO {
private String firstName;
private String lastName;
/* getters and setters ... */
}
My Spring RestController:
#RestController
#RequestMapping("/api/people")
public class PersonController {
#Autowired
private PersonRepository people;
#Transactional
#RequestMapping(path = "/{personId}", method = RequestMethod.PUT)
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody PersonDTO dto) {
// get the entity by ID
Person p = people.findOne(personId); // we assume it exists
// update ONLY entity attributes that have been defined
if(/* dto.getFirstName is defined */)
p.setFirstName = dto.getFirstName;
if(/* dto.getLastName is defined */)
p.setLastName = dto.getLastName;
return ResponseEntity.ok(p);
}
}
Request with missing property
{"firstName": "John"}
Expected behaviour: update firstName= "John" (leave lastName unchanged).
Request with null property
{"firstName": "John", "lastName": null}
Expected behaviour: update firstName="John" and set lastName=null.
I cannot distinguish between these two cases, sincelastName in the DTO is always set to null by Jackson.
Note:
I know that REST best practices (RFC 6902) recommend using PATCH instead of PUT for partial updates, but in my particular scenario I need to use PUT.
Another option is to use java.util.Optional.
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Optional;
#JsonInclude(JsonInclude.Include.NON_NULL)
private class PersonDTO {
private Optional<String> firstName;
private Optional<String> lastName;
/* getters and setters ... */
}
If firstName is not set, the value is null, and would be ignored by the #JsonInclude annotation. Otherwise, if implicitly set in the request object, firstName would not be null, but firstName.get() would be. I found this browsing the solution #laffuste linked to a little lower down in a different comment (garretwilson's initial comment saying it didn't work turns out to work).
You can also map the DTO to the Entity with Jackson's ObjectMapper, and it will ignore properties that were not passed in the request object:
import com.fasterxml.jackson.databind.ObjectMapper;
class PersonController {
// ...
#Autowired
ObjectMapper objectMapper
#Transactional
#RequestMapping(path = "/{personId}", method = RequestMethod.PUT)
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody PersonDTO dto
) {
Person p = people.findOne(personId);
objectMapper.updateValue(p, dto);
personRepository.save(p);
// return ...
}
}
Validating a DTO using java.util.Optional is a little different as well. It's documented here, but took me a while to find:
// ...
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
// ...
private class PersonDTO {
private Optional<#NotNull String> firstName;
private Optional<#NotBlank #Pattern(regexp = "...") String> lastName;
/* getters and setters ... */
}
In this case, firstName may not be set at all, but if set, may not be set to null if PersonDTO is validated.
//...
import javax.validation.Valid;
//...
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody #Valid PersonDTO dto
) {
// ...
}
Also might be worth mentioning the use of Optional seems to be highly debated, and as of writing Lombok's maintainer(s) won't support it (see this question for example). This means using lombok.Data/lombok.Setter on a class with Optional fields with constraints doesn't work (it attempts to create setters with the constraints intact), so using #Setter/#Data causes an exception to be thrown as both the setter and the member variable have constraints set. It also seems better form to write the Setter without an Optional parameter, for example:
//...
import lombok.Getter;
//...
#Getter
private class PersonDTO {
private Optional<#NotNull String> firstName;
private Optional<#NotBlank #Pattern(regexp = "...") String> lastName;
public void setFirstName(String firstName) {
this.firstName = Optional.ofNullable(firstName);
}
// etc...
}
There is a better option, that does not involve changing your DTO's or to customize your setters.
It involves letting Jackson merge data with an existing data object, as follows:
MyData existingData = ...
ObjectReader readerForUpdating = objectMapper.readerForUpdating(existingData);
MyData mergedData = readerForUpdating.readValue(newData);
Any fields not present in newData will not overwrite data in existingData, but if a field is present it will be overwritten, even if it contains null.
Demo code:
ObjectMapper objectMapper = new ObjectMapper();
MyDTO dto = new MyDTO();
dto.setText("text");
dto.setAddress("address");
dto.setCity("city");
String json = "{\"text\": \"patched text\", \"city\": null}";
ObjectReader readerForUpdating = objectMapper.readerForUpdating(dto);
MyDTO merged = readerForUpdating.readValue(json);
Results in {"text": "patched text", "address": "address", "city": null}
Note that text and city were patched (city is now null) and that address was left alone.
In a Spring Rest Controller you will need to get the original JSON data instead of having Spring deserialize it in order to do this. So change your endpoint like this:
#Autowired ObjectMapper objectMapper;
#RequestMapping(path = "/{personId}", method = RequestMethod.PATCH)
public ResponseEntity<?> update(
#PathVariable String personId,
#RequestBody JsonNode jsonNode) {
RequestDTO existingData = getExistingDataFromSomewhere();
ObjectReader readerForUpdating = objectMapper.readerForUpdating(existingData);
RequestDTO mergedData = readerForUpdating.readValue(jsonNode);
...
}
Use boolean flags as jackson's author recommends.
class PersonDTO {
private String firstName;
private boolean isFirstNameDirty;
public void setFirstName(String firstName){
this.firstName = firstName;
this.isFirstNameDirty = true;
}
public String getFirstName() {
return firstName;
}
public boolean hasFirstName() {
return isFirstNameDirty;
}
}
Actually,if ignore the validation,you can solve your problem like this.
public class BusDto {
private Map<String, Object> changedAttrs = new HashMap<>();
/* getter and setter */
}
First, write a super class for your dto,like BusDto.
Second, change your dto to extend the super class, and change the
dto's set method,to put the attribute name and value to the
changedAttrs(beacause the spring would invoke the set when the
attribute has value no matter null or not null).
Third,traversal the map.
I have tried to solve the same problem. I found it quite easy to use JsonNode as the DTOs. This way you only get what is submitted.
You will need to write a MergeService yourself that does the actual work, similar to the BeanWrapper. I haven't found an existing framework that can do exactly what is needed. (If you use only Json requests you might be able to use Jacksons readForUpdate method.)
We actually use another node type as we need the same functionality from "standard form submits" and other service calls. Additionally the modifications should be applied within a transaction inside something called EntityService.
This MergeService will unfortunately become quite complex, as you will need to handle properties, lists, sets and maps yourself :)
The most problematic piece for me was to distinguish between changes within an element of a list/set and modifications or replacements of lists/sets.
And also validation will not be easy as you need to validate some properties against another model (the JPA entities in my case)
EDIT - Some mapping code (pseudo-code):
class SomeController {
#RequestMapping(value = { "/{id}" }, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public void save(
#PathVariable("id") final Integer id,
#RequestBody final JsonNode modifications) {
modifierService.applyModifications(someEntityLoadedById, modifications);
}
}
class ModifierService {
public void applyModifications(Object updateObj, JsonNode node)
throws Exception {
BeanWrapperImpl bw = new BeanWrapperImpl(updateObj);
Iterator<String> fieldNames = node.fieldNames();
while (fieldNames.hasNext()) {
String fieldName = fieldNames.next();
Object valueToBeUpdated = node.get(fieldName);
Class<?> propertyType = bw.getPropertyType(fieldName);
if (propertyType == null) {
if (!ignoreUnkown) {
throw new IllegalArgumentException("Unkown field " + fieldName + " on type " + bw.getWrappedClass());
}
} else if (Map.class.isAssignableFrom(propertyType)) {
handleMap(bw, fieldName, valueToBeUpdated, ModificationType.MODIFY, createdObjects);
} else if (Collection.class.isAssignableFrom(propertyType)) {
handleCollection(bw, fieldName, valueToBeUpdated, ModificationType.MODIFY, createdObjects);
} else {
handleObject(bw, fieldName, valueToBeUpdated, propertyType, createdObjects);
}
}
}
}
Maybe too late for an answer, but you could:
By default, don't unset 'null' values. Provide an explicit list via query params what fields you want to unset. In such a way you can still send JSON that corresponds to your entity and have flexibility to unset fields when you need.
Depending on your use case, some endpoints may explicitly treat all null values as unset operations. A little bit dangerous for patching, but in some circumstances might be an option.
Another solution would be to imperatively deserialize the request body. By doing it, you will be able to collect user provided fields and selectively validate them.
So your DTO might look like this:
public class CatDto {
#NotBlank
private String name;
#Min(0)
#Max(100)
private int laziness;
#Max(3)
private int purringVolume;
}
And your controller can be something like this:
#RestController
#RequestMapping("/api/cats")
#io.swagger.v3.oas.annotations.parameters.RequestBody(
content = #Content(schema = #Schema(implementation = CatDto.class)))
// ^^ this passes your CatDto model to swagger (you must use springdoc to get it to work!)
public class CatController {
#Autowired
SmartValidator validator; // we'll use this to validate our request
#PatchMapping(path = "/{id}", consumes = "application/json")
public ResponseEntity<String> updateCat(
#PathVariable String id,
#RequestBody Map<String, Object> body
// ^^ no Valid annotation, no declarative DTO binding here!
) throws MethodArgumentNotValidException {
CatDto catDto = new CatDto();
WebDataBinder binder = new WebDataBinder(catDto);
BindingResult bindingResult = binder.getBindingResult();
List<String> patchFields = new ArrayList<>();
binder.bind(new MutablePropertyValues(body));
// ^^ imperatively bind to DTO
body.forEach((k, v) -> {
patchFields.add(k);
// ^^ collect user provided fields if you need
validator.validateValue(CatDto.class, k, v, bindingResult);
// ^^ imperatively validate user input
});
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(null, bindingResult);
// ^^ this can be handled by your regular exception handler
}
// Here you can do normal stuff with your catDto.
// Map it to cat model, send to cat service, whatever.
return ResponseEntity.ok("cat updated");
}
}
No need for Optional's, no extra dependencies, your normal validation just works, your swagger looks good. The only problem is, you don't get proper merge patch on nested objects, but in many use cases that's not even required.
Probably to late but following code works for me to distinguish between null and not provided values
if(dto.getIban() == null){
log.info("Iban value is not provided");
}else if(dto.getIban().orElse(null) == null){
log.info("Iban is provided and has null value");
}else{
log.info("Iban value is : " + dto.getIban().get());
}

Rename ObjectId _id to id in jackson deserialization with Jongo and MongoDB

I've just started working on a project using the play framework,jongo and MongoDB. The project was initially written in Play 2.1 with pojos with an String id field annotated with both: #Id and #ObjectId This would persist to Mongo as an ObjectId and when deserialized would output the id as: "id":"53fcb9ede4b0b18314098d10" for example.
Since upgrading to Jongo 1.1 and Play 2.3.3 the id attribute is always named "_id" when deserialized, I want the attribute to retain the field name yet I can't use #JsonProperty("custom_name") as the Jongo #Id annotation does #JsonProperty("_id") behind the scenes.
import org.jongo.marshall.jackson.oid.Id;
import org.jongo.marshall.jackson.oid.ObjectId;
public class PretendPojo {
#Id
#ObjectId
private String id;
private String name;
public PretendPojo() {
}
public PretendPojo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
The POJOs when persisted in MongoDB look like this if I view them via RoboMongo
{
"_id" : ObjectId("53fc984de4b0c34f1905b8ee"),
"name" : "Owen"
}
However when I deserialize them I get the following json if I keep both annotations:
{"name":"Owen","_id":{"time":1409072858000,"date":1409072858000,"timestamp":1409072858,"new":false,"timeSecond":1409072858,"inc":308487737,"machine":-458223042}}
and the following output if I only use the #Id annotation.
{"name":"Owen","_id":"53fcbedae4b0123e12632639"}
I have a test case for working with the PretendPojo show above:
#Test
public void testJongoIdDeserialization() throws UnknownHostException {
DB database = new MongoClient("localhost", 27017).getDB("jongo");
Jongo jongo = new Jongo(database);
MongoCollection collection = jongo.getCollection("jongo");
collection.save(new PretendPojo("Owen"));
PretendPojo pretendPojo = collection.findOne("{name: \"Owen\"}").as(PretendPojo.class);
JsonNode json = Json.toJson(pretendPojo);
assertNotNull(json.get("id"));
}
When trying to use custom deserializers I never can get hold of the object ID I seem to only have access to the date/time/timestamp data that is currently being deserialized.
Ideally the output I'm looking for would be:
{"name":"Owen","id":"53fcbedae4b0123e12632639"}
Any help will be greatly appreciated! :)
ObjectIdSerializer always writes property mapped with #ObjectId to a new instance of ObjectId. This is wrong when you map this property to a String.
To avoid this behaviour, I've write a NoObjectIdSerializer :
public class NoObjectIdSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(value);
}
}
used like this :
#ObjectId
#JsonSerialize(using = NoObjectIdSerializer.class)
protected final String _id;
There is an open issue.
I think that there is an annotation in jackson that allows you to change the property name, I think is : #JsonProperty but you can see all the possible annotations in this link:
https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations
I hope this solve your problem
You can try using #JsonValue, Jongo does not seem to use them, but without any response from the developers this behaviour might be subject to change in future releases.
#JsonValue
public Map<String, Object> getJson() {
Map<String, Object> map = new HashMap<>();
map.put("name", name);
map.put("id", id);
return map;
}
The more proper solution would be to try combining #JsonView with #Id annotation
Remember to specify which View to use on Jongo's ObjectMapper and your Jackson ObjectMapper (the one to use in REST layer, I presume)
#Id
#JsonView(Views.DatabaseView.class)
private String id;
#JsonView(Views.PublicView.class)
public String getId() {
return id;
}
The Jongo's behaviour has changed since 1.1 for a more consistent handling of its owns annotations.
If your '_id' is a String and you want this field to be stored into Mongo as a String then only #Id is needed.
#Id + #ObjectId on a String property means :
"My String property named 'foo' is a valid ObjectId. This property has to be stored with the name '_id' and have to be handled as an ObjectId."

Categories