Spring API Boolean Parameter Strict Validation - java

I wish to perform strict validation on a boolean parameter, seen below as "withDetails."
#ApiResponses(value = {#ApiResponse(responseCode = "200", description = "Success")})
#GetMapping(value = "/order", produces = "application/json")
public ResponseEntity<ResponseClass> getOrders( String id,
#RequestParam(value = "withDetails", defaultValue = "true")
Boolean withDetails){
ResponseClass responseClass = this.service.getResponse(id, withDetails);
return ResponseEntity.ok(responseClass);
}
Spring accepts non-boolean substitutions for this value, such as 0/1. This is apparently by design, as described here:
https://stackoverflow.com/a/25123000/11994829
However, I want to strictly only allow "true/false" in requests. Can I do that while still defining the parameter as Boolean?

Try to create your own Converter:
#Component
public class BooleanConverter implements Converter<String, Boolean> {
#Override
public Boolean convert(String bool) {
if ("true".equals(bool)) {
return true;
}
if ("false".equals(bool)) {
return false;
}
throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
}
}

In spring Boolean parameters have a value of 1/0 if you want to catch the value
boolean type make parameter as String String withDetails and convert it .valueOf into boolean type (true\false)
or you to make your own converter convert 1 as true and 0 as false

Related

method count(JobViewWrapper) is already defined

I'm currently learning spring boot and encounter an error message
method count(JobViewWrapper) is already defined
yes it's because it has 2 of the same method name , but the method has 2 separate function , the first one is to count all the job with deleted flag 1.
the second one (count-active), is to count all the job with deleted flag 1 and is active flag 1.
So i needed this 2 method, is there a workaround to do it?
#PostMapping(value = "/count")
public long count(#RequestBody(required = false) JobViewWrapper wrapper) {
System.out.println("into controller count");
if (wrapper == null) {
wrapper = new JobViewWrapper();
}
System.out.println("Prepare to count service");
return JobService.countLazyView();
}
#PostMapping(value = "/count-active")
public long count(#RequestBody(required = false) JobViewWrapper wrapper) {
System.out.println("into controller count");
if (wrapper == null) {
wrapper = new JobViewWrapper();
}
System.out.println("Prepare to count service");
return JobService.countLazyViewIsActive();
}
my service
public long countLazyView() {
return lowonganKerjaRepo.countLazyView();
}
public long countLazyViewIsActive() {
return lowonganKerjaRepo.countLazyViewIsActive();
}
If you really want to overload method count you must select one of the three options available:
provide different number of parameters:
#PostMapping(value = "/count")
public long count(#RequestBody(required = false) JobViewWrapper wrapper) {
// ...
return JobService.countLazyView();
}
#PostMapping(value = "/count-active")
public long count() {
return JobService.countLazyViewIsActive();
}
provide different types of parameters:
#PostMapping(value = "/count")
public long count(#RequestBody(required = false) JobViewWrapper wrapper) {
// ...
return JobService.countLazyView();
}
#PostMapping(value = "/count-active")
public long count(#RequestBody(required = false) ActiveJobViewWrapper wrapper) {
return JobService.countLazyViewIsActive();
}
provide different order of parameters (seems to be not applicable in this case).
If none of these options can be selectable, you'll have options:
Provide different names for these methods count() and countActive
Replace these methods with one method having additional parameter (API call may be changed to /count?active=true):
#PostMapping(value = "/count")
public long count(
#RequestParam(name = "active", required = false, defaultValue = "false") Boolean active,
#RequestBody(required = false) JobViewWrapper wrapper) {
// ...
return active ? JobService.countLazyViewIsActive() : JobService.countLazyView();
}

Handling boolean values and empty strings for a property in Jackson?

I have a JSON property that can be one of
{ "observed": true }
{ "observed": false }
{ "observed": "" }
I'd like to map it so that in Java it will one of "true", "false" or ""
#JsonProperty("observed")
private String observedValue;
Then I'll just make a getter that would give me a
public Optional<Boolean> getObservedOpt() {
if ("".equals(observedValue)) {
return Optional.empty();
} else {
return Optional.of(Boolean.parseBoolean(observedValue));
}
}
However, I am not sure how to make it converted true and false into strings. Or perhaps there's a more elegant way of doing it without the string comparison.
I would suggest configure object mapper with this feature ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, so in case of empty string it will be assigned to null
ObjectMapper mapper = new ObjectMapper()
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
And you can happily declare this field as Boolean type, be aware in case of empty string this field value will be null
#JsonProperty("observed")
private Boolean observedValue;
One solution could be using Custom JsonDeserializer
public static class StringBooleanDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException {
if(parser.getValueAsBoolean()){
return "true";
}else{
if(parser.getTextLength()==0) {
return parser.getText();
}else{
return "false";
}
}
}
}
#JsonProperty("observed")
#JsonDeserialize(using=StringBooleanDeserializer.class)
private String observedValue;
Similarly, you can also write custom JSON Serializer.

How to implement single DeleteMapping for 2 parameters?

I have a user table in my database with 2 fields id and name. I want to create an API to delete a user on the bases of id or name. Like this:
#DeleteMapping(path = "/{name}")
public ResponseEntity<Object> clearUser(#PathVariable("name") String name){
myService.deleteUser(name);
return new ResponseEntity<>(HttpStatus.OK);
}
#DeleteMapping(path = "/{id}")
public ResponseEntity<Object> clearUser(#PathVariable("id") int id){
myService.deleteUser(id);
return new ResponseEntity<>(HttpStatus.OK);
}
But I want to do it under one #DeleteMapping and I have to do it using path param only not query param. A person can enter id or name to delete that user.
You might use your two path variables as optional.
#DeleteMapping(path = {"/{name}", "/{id}")
public ResponseEntity<Object> clearUser(#PathVariable("name") Optional<String> name, #PathVariable("id") Optional<Integer> id){
myService.deleteByIdOrName(id, name);
return new ResponseEntity<>(HttpStatus.OK);
}
MyRepo ...
void deleteByIdOrName(Optional<Integer> id, Optional<String> name);
MyService
void deleteByIdOrName(Optional<Integer> id, Optional<String> name) {
repo.deleteByIdOrName(id, name);
}
Just check the path parameter type whether it is numeric or a String. Based on that, you can change the implementation under one method. just a hint:
#DeleteMapping(path = "/{user}")
public ResponseEntity clearUser(#PathVariable("user") String user){
myService.deleteUser(user);
return new ResponseEntity<>(HttpStatus.OK);
}
and inside the implementation, do the following:
use Java's built-in java.text.NumberFormat object to see if, after
parsing the string the parser position is at the end of the string. If
it is, we can assume the entire string is numeric:
public static boolean isNumeric(String str) {
NumberFormat formatter =
NumberFormat.getInstance();
ParsePosition pos = new
ParsePosition(0);
formatter.parse(str, pos);
return str.length() == pos.getIndex();
}
....
if(isNumeric(user)){
// implement delete by ID
}else{
//implement delete by Name
}
....
Hope this will give you an idea..

Incompatible types while returning Either

I'm using vavr's Either to control flow of my application and it worked fine, until I didn't have to mix few domains...
First of all, I got one interface that is shared between domains.
public interface DomainError {
String getCause();
}
And same for success, when I just want to inform user about that and don't need to return particular object.
#RequiredArgsConstructor
#Getter
public class SuccessMessage {
private final String message;
}
Now, each domain implements DomainError like this
public enum UserError implements DomainError {
USERNAME_ALREADY_EXISTS("Username already exists"),
INVALID_EMAIL("Provided email address is invalid");
private final String cause;
UserError(String cause) {
this.cause = cause;
}
#Override
public String getCause() {
return cause;
}
}
Similar it looks for EmailError which just have another values inside. Now, I have EmailFacade
public class EmailFacade {
private final SendEmailUseCase sendEmail;
private final CreateEmailUseCase createEmail;
public Either<DomainError, SuccessMessage> sendUserVerificationEmail(UUID uuid, String receiver) {
return sendEmail.send(createEmail.createUserVerificationEmail(uuid, receiver));
}
}
And one of it's services returns
class SendEmailUseCase {
Either<DomainError, SuccessMessage> send(EmailMessage message) {
if(message == null) return Either.left(EmailError.EMPTY_MESSAGE);
log.info(message.toString());
return Either.right(new SuccessMessage("Email sent"));
}
}
Now, I need to call this from my other domain, which handles registration of user.
In order to do that I wrote this method
Either<DomainError, SuccessMessage> register(RegisterUserDto registerUserDto) {
if(userRepository.findUser(registerUserDto.getUsername()).isPresent())
return Either.left(UserError.USERNAME_ALREADY_EXISTS);
Either<DomainError, User> userCreationResult = User.createUser(registerUserDto);
return userCreationResult
.map(user -> {
userRepository.save(user.toDto());
final UUID uuid = verificationTokenRepository.generateVerificationToken(user.getUsername());
return emailFacade.sendUserVerificationEmail(uuid, user.getUsername());
});
}
I'm doing few things here, but relevant part is last return statement, all above is working. emailFacade.sendUserVerificationEmail() returns Either<DomainError, SuccessMessage>, same as register() method. But for some reason, I'm getting this error
Error:(28, 17) java: incompatible types: inference variable U has
incompatible bounds
equality constraints: com.johndoe.myapp.domain.SuccessMessage
lower bounds: io.vavr.control.Either<com.johndoe.myapp.domain.DomainError,com.johndoe.myapp.domain.SuccessMessage>
And I seriously can't understand what went wrong here...
Method map is used to transform value inside container. In your case it gives you value of User and expects that you return transformed value. So in case you are returning Either<DomainError, User> then type of result will be Either<DomainError, Either<DomainError, User>> and it is not what method signature expects as return type.
If you wan't to return Either from function, then you should use flatMap instead, which would flatten result to Either<DomainError, User>:
return userCreationResult
.flatMap(user -> { //replace map with flatMap
userRepository.save(user.toDto());
final UUID uuid = verificationTokenRepository.generateVerificationToken(user.getUsername());
return emailFacade.sendUserVerificationEmail(uuid, user.getUsername());
});

Multiple #QueryParam keys for a single value in Jersey

Is it possible to allow multiple #QueryParam keys for a single object/variable in Jersey?
Actual:
#POST
public Something getThings(#QueryParam("customer-number") Integer n) {
...
}
so, if I add ?customer-number=3 after the URL it works.
Expected:
I want to get the behavior above if I add any of the following values:
?customer-number=3
?customerNumber=3
?customerNo=3
Obs:
The QueryParam annotation looks like:
...
public #interface QueryParam {
String value();
}
so, it cannot accept multiple String values (like #Produces).
The approach below allows the user to use multiple keys having the same meaning at the same time (and I want to have an "OR" condition between them):
#POST
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) {
...
}
Something like this doesn't work:
#POST
public Something getThings(#QueryParam("customer-number|customerNumber|customerNo") Integer n) {
...
}
How can I do this?
Details:
Jersey 2.22.1
Java 8
To be honest: this is not how webservices are supposed to be designed. You lay down a strict contract that both client and server follow; you define one parameter and that's it.
But of course it would be a perfect world where you have the freedom to dictate what is going to happen. So if you must allow three parameters in, then you'll have to make that the contract. This is one way following approach #2 which I have to provide without being able to test it for goofs:
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) throws YourFailureException {
Integer customerNumber = getNonNullValue("Customer number", n1, n2, n3);
// things with stuff
}
private static Integer getNonNullValue(String label, Integer... params) throws YourFailureException {
Integer value = null;
for(Integer choice : params){
if(choice != null){
if(value != null){
// this means there are at least two query parameters passed with a value
throw new YourFailureException("Ambiguous " + label + " parameters");
}
value = choice;
}
}
if(value == null){
throw new YourFailureException("Missing " + label + " parameter");
}
return value;
}
So basically reject any call that does not pass specifically one of the parameters, and let an exception mapper translate the exception you throw into a HTTP response code in the 4xx range of course.
(I made the getNonNullValue() method static is it strikes me as a reusable utility function).
Maybe the simplest and easiest way would be to use a custom #BeanParam:
First define the custom bean merging all the query parameters as:
class MergedIntegerValue {
private final Integer value;
public MergedIntegerValue(
#QueryParam("n1") Integer n1,
#QueryParam("n2") Integer n2,
#QueryParam("n3") Integer n3) {
this.value = n1 != null ? n1
: n2 != null ? n2
: n3 != null ? n3
: null;
// Throw an exception if value == null ?
}
public Integer getValue() {
return value;
}
}
and then use it with #BeanParam in your resource method:
public Something getThings(
#BeanParam MergedIntegerValue n) {
// Use n.getValue() ...
}
Reference: https://jersey.java.net/documentation/latest/user-guide.html#d0e2403
You can create a custom annotation. I won't go in too much about how to do it, you can see this post, or this post. Basically it relies on a different infrastructure than the usual dependency injection with Jersey. You can see this package from the Jersey project. This is where all the injection providers live that handle the #XxxParam injections. If you examine the source code, you will see the the implementations are fairly the same. The two links I provided above follow the same pattern, as well as the code below.
What I did was created a custom annotation
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface VaryingParam {
String value();
#SuppressWarnings("AnnotationAsSuperInterface")
public static class Factory
extends AnnotationLiteral<VaryingParam> implements VaryingParam {
private final String value;
public static VaryingParam create(final String newValue) {
return new Factory(newValue);
}
public Factory(String newValue) {
this.value = newValue;
}
#Override
public String value() {
return this.value;
}
}
}
It may seem odd that I have a factory to create it, but this was required for the implementation of the below code, where I split the value of the String, and end up creating a new annotation instance for each split value.
Here is the ValueFactoryProvider (which, if you've read either of the above articles, you will see that is required for custom method parameter injection). It a large class, only because I put all the required classes into a single class, following the pattern you see in the Jersey project.
public class VaryingParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public VaryingParamValueFactoryProvider(
final MultivaluedParameterExtractorProvider mpep,
final ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
VaryingParam annotation = parameter.getAnnotation(VaryingParam.class);
if (annotation == null) {
return null;
}
String value = annotation.value();
if (value == null || value.length() == 0) {
return null;
}
String[] variations = value.split("\\s*\\|\\s*");
return new VaryingParamFactory(variations, parameter);
}
private static Parameter cloneParameter(final Parameter original, final String value) {
Annotation[] annotations = changeVaryingParam(original.getAnnotations(), value);
Parameter clone = Parameter.create(
original.getRawType(),
original.getRawType(),
true,
original.getRawType(),
original.getRawType(),
annotations);
return clone;
}
private static Annotation[] changeVaryingParam(final Annotation[] annos, final String value) {
for (int i = 0; i < annos.length; i++) {
if (annos[i] instanceof VaryingParam) {
annos[i] = VaryingParam.Factory.create(value);
break;
}
}
return annos;
}
private class VaryingParamFactory extends AbstractContainerRequestValueFactory<Object> {
private final String[] variations;
private final Parameter parameter;
private final boolean decode;
private final Class<?> paramType;
private final boolean isList;
private final boolean isSet;
VaryingParamFactory(final String[] variations, final Parameter parameter) {
this.variations = variations;
this.parameter = parameter;
this.decode = !parameter.isEncoded();
this.paramType = parameter.getRawType();
this.isList = paramType == List.class;
this.isSet = paramType == Set.class;
}
#Override
public Object provide() {
MultivaluedParameterExtractor<?> e = null;
try {
Object value = null;
MultivaluedMap<String, String> params
= getContainerRequest().getUriInfo().getQueryParameters(decode);
for (String variant : variations) {
e = get(cloneParameter(parameter, variant));
if (e == null) {
return null;
}
if (isList) {
List list = (List<?>) e.extract(params);
if (value == null) {
value = new ArrayList();
}
((List<?>) value).addAll(list);
} else if (isSet) {
Set set = (Set<?>) e.extract(params);
if (value == null) {
value = new HashSet();
}
((Set<?>) value).addAll(set);
} else {
value = e.extract(params);
if (value != null) {
return value;
}
}
}
return value;
} catch (ExtractorException ex) {
if (e == null) {
throw new ParamException.QueryParamException(ex.getCause(),
parameter.getSourceName(), parameter.getDefaultValue());
} else {
throw new ParamException.QueryParamException(ex.getCause(),
e.getName(), e.getDefaultValueString());
}
}
}
}
private static class Resolver extends ParamInjectionResolver<VaryingParam> {
public Resolver() {
super(VaryingParamValueFactoryProvider.class);
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(VaryingParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(VaryingParamValueFactoryProvider.Resolver.class)
.to(new TypeLiteral<InjectionResolver<VaryingParam>>() {
})
.in(Singleton.class);
}
}
}
You will need to register this class' Binder (bottom of class) with Jersey to use it.
What differentiates this class from Jersey QueryParamValueFactoryProvider is that instead of just processing a single String value of the annotation, it splits the value, and tries to extract the values from the query param map. The first value found will be returned. If the parameter is a List or Set, it just continues to keep looking up all the options, and adding them to the list.
For the most part this keeps all the functionality you would expect from an #XxxParam annotation. The only thing that was difficult to implement (so I left out supporting this use case), is multiple parameters, e.g.
#GET
#Path("multiple")
public String getMultipleVariants(#VaryingParam("param-1|param-2|param-3") String value1,
#VaryingParam("param-1|param-2|param-3") String value2) {
return value1 + ":" + value2;
}
I actually don't think it should be that hard to implement, if you really need it, it's just a matter of creating a new MultivaluedMap, removing a value if it is found. This would be implemented in the provide() method of the VaryingParamFactory above. If you need this use case, you could just use a List or Set instead.
See this GitHub Gist (it's rather long) for a complete test case, using Jersey Test Framework. You can see all the use cases I tested in the QueryTestResource, and where I register the Binder with the ResourceConfig in the test configure() method.

Categories