I am working on a spring boot project. I am building a PUT/POST API which takes boolean data type in the request body. The issue is whenever I pass an integer value like 12,13,-15... etc it converts it to true (which is the default behavior of boolean), but my doubt is, is their a way to validate here such that it takes only 0,1, true and false. I tried adding the regex to validation.properties and #ESAPI annotations to my model, but I've read that data canonicalization happens before validation occurs. so I am stuck with this issue, How do I resolve this?
This is a PUT Request.
Here is the Request body
{
"access": true,
"prefLanguage": "English",
"prefTimeZone": "USA"
}
My Model is defined as
public class Consent implements Serializable {
#ESAPIPattern(validateWithPattern = "acess", required = true)
private String acess;
#JsonInclude(JsonInclude.Include.NON_NULL)
#ESAPIPattern(allowNull = true,validateWithPattern = "prefTimeZone")
private String prefTimeZone;
#JsonInclude(JsonInclude.Include.NON_NULL)
#ESAPIPattern(allowNull = true,validateWithPattern = "prefLanguage")
private String prefLanguage;
... getters and setters
}
Here I want "access" to accept only true|false or 0|1, I don't want it to accept any other integer value.
You can use a context validator which is provided by spring. You can create an annotation #BoolenValidator and put it in the request param or path variable.
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean isValid = true;
if (isNull(value) || !(value==0|| value==1)) {
isValid = addViolation(value, "must be 0 or 1");
}
return isValid;
}
Related
so currently I'm working on a project where we have product objects which in turn contain "Origin" objects (containing region: String and country: String).
What I'm trying to do is a RestController which takes in an optional Origin object and does something with it (e.g. logs it).
This is what I have right now:
#GetMapping("search")
public Page<Wine> getProductByStuff(
#RequestParam(required = false) Origin origin,
/* other attributes */) {
log.info(origin); // it has a proper toString method.
}
There are two problem with this approach. First of all, when I send a request like:
http://[...]/search?origin={"region":"blah","country":"UK"}
or even the html converted string like:
http://[...]/search?origin={%22region%22:%22blah%22%44%22country%22:%22UK%22}
... it says
Invalid character found in the request target [/api/products/search?origin={%22region%22:%22blah%22%44%22country%22:%22DE%22}]. The valid characters are defined in RFC 7230 and RFC 3986.
Afaik the only valid characters Tomcat has that I need are {}. All others I've replaced with the html encoded chars and it still doesn't work.
What I did to prevent this:
#Component
public class TomcatWebServerCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
TomcatConnectorCustomizer a = null;
factory.addConnectorCustomizers(connector -> {
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|},\"");
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|},\"");
});
}
}
(See this, which is, by the way, deprecated (at least connector.setAttribute).)
This produced:
MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type '[censored].backend.model.Origin'.
My questions are:
(How) is it possible to configure Tomcat/Spring so that they can actually accept json in the url params?
How would I format it in e.g. Postman so that it would work? Currently I'm just converting special characters by hand in the params tab of Postman.
Here is what you need to do if you want to send it as json query param.
#RestController
public class OriginController {
#GetMapping("/search")
public void getOrigin(#RequestParam(value = "origin", required = false)
Origin origin) {
System.out.println(origin);
}
}
Register a converter
#Component
public class StringToOriginConverter implements
Converter<String, Origin> {
ObjectMapper objectMapper = new ObjectMapper();
#Override
public Origin convert(String source) {
try {
return objectMapper.readValue(source, Origin.class);
} catch (JsonProcessingException e) {
//You could throw some exception here instead for custom error
return null;
}
}
}
Sending from postman
Note
My answer is not debating whether you should use POST or GET as it is not what you have asked. It is just providing one option if you want to send some payload as query param
As mentioned, don't use JSON as a path parameter.
Directly use path parameters, and convert to Origin object.
#GetMapping("search")
public Page<Wine> getProductByStuff(
#RequestParam(required = false) String region,
#RequestParam(required = false) String country, /* other attributes */) {
Origin origin = new Origin(region, country);
log.info(origin); // it has a proper toString method.
}
This question already has answers here:
How to distinguish between null and not provided values for partial updates in Spring Rest Controller
(8 answers)
Closed 2 years ago.
I have a PATCH REST endpoint, exposing a JSON interface, which can be used to partially update an entity, based on the attributes which are actually sent in the body.
Let's consider a sample class representing the entity:
class Account {
private UUID accountUuid;
private UUID ownerUuid;
private String number;
private String alias;
// constructor, getters and setters omitted
}
where accountUuid, ownerUuid and number are required properties, and alias is optional. Additionally I have a command class for updating said account:
class UpdateAccountCommand {
private UUID accountUuid;
private String number;
private String alias;
// constructor, getters and setters omitted
}
For my PATCH endpoint, e.g. PATCH /accounts/{account-uuid}, I'd like to implement a functionality, that only properties actually sent in the request are changed:
// this should only update the account number
{"number": "123456"}
// this should only update the alias, by setting it to null
{"alias": null}
// this shouldn't update anything
{}
For required properties, this is fairly easy to do. After deserialisation from the JSON string to UpdateAccountCommand instance using Jackson, I simply check if a required property is null, and when it's not null, I update the given property.
However the situation complicates with optional properties, since the alias property is null in both cases:
when the request body explicitly specifies the alias as null,
when the alias property is not specified in the request body at all.
How can I model these optional properties, so that I can indicate this removable mechanism?
A naive solution would be to introduce some sort of a wrapper, which would not only contain the raw value (e.g. for the alias: string property), but also a boolean, indicating whether a property was specified in the body or not. This would require you to write a custom deserialisation mechanism, which can be a tedious work.
Since the question is about Java 8, for Java 8 and newer, I recommend using a nullable Optional, which works pretty much out of the box with Jackson.
For optional (removable fields), you change the raw values by wrapping them in optional:
class UpdateAccountCommand {
private UUID accountUuid;
private String number;
private Optional<String> alias;
// constructor, getters and setters omitted
}
In order for Jackson to work with Optional<*> fields correctly, the Jdk8Module module has to be registered to the object mapper, e.g.:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());
The following code:
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());
String withNewAliasS = "{\"alias\":\"New Alias\"}";
String withNullAliasS = "{\"alias\":null}";
String withoutPropertyS = "{}";
UpdateAccountCommand withNewAlias = objectMapper.readValue(withNewAliasS, UpdateAccountCommand.class);
if (withNewAlias.getAlias() != null && withNewAlias.getAlias().isPresent()) {
System.out.println("withNewAlias specified new alias.");
}
UpdateAccountCommand withNullAlias = objectMapper.readValue(withNullAliasS, UpdateAccountCommand.class);
if (withNullAlias.getAlias() != null && !withNullAlias.getAlias().isPresent()) {
System.out.println("withNullAlias will remove an alias.");
}
UpdateAccountCommand withoutProperty = objectMapper.readValue(withoutPropertyS, UpdateAccountCommand.class);
if (withoutProperty.getAlias() == null) {
System.out.println("withoutProperty did not contain alias property on input at all.");
}
}
then prints out this to the console:
withNewAlias specified new alias.
withNullAlias will remove an alias.
withoutProperty did not contain alias property on input at all.
you can add an additional boolean property which says if the optional property was present in request
class UpdateAccountCommand {
//...
private String alias;
#JsonIgnore
private boolean isAliasSet;
#JsonProperty
public void setAlias(String value) {
this.alias = value;
this.isAliasSet = true;
}
}
the setter is called only when "alias" is present, be it null or with value
We got this REST endpoint in which one of the field is mapped to a Boolean (The wrapper class) instance. We are using a Boolean instead of a boolean because design decision, so this is non-negotiable.
This Boolean value is mandatory and it must be specified by the sender ("whateverValue":"" should return a 400 error), but when arriving to the endpoint, the value is automatically converted to a correct false value.
So, the question is: Can this be done? Are we not understanding the contract of using a "Boolean" object instead of the primitive?
EDIT: Just to clarify, we are already validating "whateverValue":null, and the value can be either true or false, so, as far as I know, neither #NotNull or #AssertTrue/False can be used here.
If you want to validate the Object Type Boolean you should use #NotNull
Here is a question where this has been asked before.
I use #NotNull if a boolean is mendatory to be set ans #AssertTrue/false to verify the incoming value has a specific state.
Hope this helps
I coded your scenario as follows and it was ok!
Please correct me if my understanding (from your scenario) is incorrect.
Create DTO
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class TestDto {
#NotNull
Boolean testValue;
//getter and setter
}
Create Endpoint
#RestController
public class testController {
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void accountTrades(#RequestBody #Valid TestDto testDto) {
System.out.println(testDto.getTestValue());
}
}
Using Postman, when I send testValue:"", it throws a notnull exception, that means the server has received null, and there is no null to false conversion. (It worked correctly!)
Server response is as follows
I have a Spring-framework based web application, and I post data from client side to server side as an object using ModelAttribute annotation.
I have an Entity bean that contains data (and stored to database later). One of the fields is a boolean field:
#Entity
#Table(name="PLAYER")
public class Player implements Serializable, JSONAble{
...
#Column(name="IS_HUNGARIAN")
private boolean isHungarian
...
}
I send the data to server side with an AJAX call using jQuery:
$.ajax({
type : "POST",
url : contextPath + 'saveEditedPlayer',
data: $('#editPlayerForm').serialize(),
success : function(result) {
alert('Success');
},error:function (xhr, ajaxOptions, thrownError){
}
});
In the Controller I receive the data like this:
#RequestMapping("/saveEditedPlayer")
#ResponseBody
public String saveEditedPlayer(#ModelAttribute final Player player, #RequestParam(required = false) final String tsiAlso) {
final JSONObject result = new JSONObject();
//some more source code
return result;
}
All the fields in the Player object are filled fine except the boolean field. According to this link the reason is that boolean values posted to server side as String value, and that is true. I have a workaround solution: I have a String field in the Entity bean, where I can store the boolean data as a String value ("true" or "false"), and in the field's set method I can set the real boolean field's value (thanks to Spring it's done automatically). But I don't think this is the right way to solve this problem.
Is there a way to post my boolean variable without any kind of helper variable?
Thank you for any kind of help in advance, if you need some more information, please ask me.
You need to name your stuff like this:
Player.java:
...
private boolean hungarian;
public boolean isHungarian() {...}
public void setHungarian(boolean hungarian) {...}
...
Your request variable is then hungarian
I have the following code inside a javabean:
#AssertTrue
private boolean addressReferenceValid;
public boolean isAddressReferenceValid() {
if (addressType.equals(AddressType.ON_THE_FLY_ADDRESS) && StringUtils.isEmpty(addressReference)) {
return false;
}
return true;
}
The issue I have is that the isAddressReferenceValid accessor is never used and it seems that only the default value for addressReferenceValid is used (i.e. false).
I have double checked that Spring Roo did not generate its own accessor for that variable.
Can anyone please help?
P.S. I can't put the #AssertTrue annotation directly on the method because then the following key in ValidationMessages.properties file would not be resolved: AssertTrue.familyAdvertisementSearchCriteriaInfo.addressReferenceValid
edit 1:
Full bean:
#RooEquals
#RooJavaBean
public class FamilyAdvertisementSearchCriteriaInfo {
#Valid
private FamilyAdvertisementSearchCriteria searchCriteria;
private Address currentAddress;
private String addressReference;
#NotNull
private AddressType addressType;
#AssertTrue(groups=Default.class)
private boolean addressReferenceValid;
public boolean isAddressReferenceValid() {
if (addressType.equals(AddressType.ON_THE_FLY_ADDRESS) && StringUtils.isEmpty(addressReference)) {
return false;
}
return true;
}
}
Validation occurs in the following controller:
#RequestMapping(value = "/familyAdvertisementSearch", method = RequestMethod.POST, produces = "text/html")
public String familyAdvertisementSearchResults(#ModelAttribute #Validated(Default.class) FamilyAdvertisementSearchCriteriaInfo familyAdvertisementSearchCriteriaInfo, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()){
populateModel(model);
familyAdvertisementSearchCriteriaInfo.setCurrentAddress(memberService.retrieveCurrentMemberAddress());
return "search/familyAdvertisementSearchForm";
}
...
I think I understand now what you are trying to do. You want to place the constraint on the field, but during validation you expect the method isAddressReferenceValid to be called/used. That's not going to work. If you place a constraint on a field access is used to get the property to validate (using reflection). If you place it on a method/getter method access is used. So he placement of the annotation matters. As you already seem to have discovered placing the annotation on the method works. Of course this leads to inconsistent placement of annotations. You could:
Just place the annotation for this single constraint
switch to method annotations completely
update the Boolean every time the address type changes (and get rid of isAddressReferenceType method)
create a custom constraint for verifying the addressReference
Just some ideas. It all depends on your use case and personal preferences.