Is there a 'choice' Java annotation equivalent to <xs:choice>? - java

I am looking to field validate a web service request by only allowing one of two different fields in the request. I know from past experience from using xsd that you can have something like this to only allow either FieldOne or FieldTwo:
<xs:complexType name="SomeType">
<xs:choice>
<xs:element name="FieldOne" type="target:FieldOneType"/>
<xs:element name="FieldTwo" type="target:FieldTwoType"/>
</xs:choice>
</xs:complexType>
I would like to do the same using Java annotations. I am currently using annotations for limiting field length (#Digits) and null checks (#NotNull).
Is there something I can use for a 'choice'?
Thanks for any help.
UPDATE: Basically I am looking for some way of only allowing one of two different fields to be entered in a web service request without having to do this validation in my code manually. I am currently using bean validation annotations to limit field lengths and to determine whether a field is mandatory or optional e.g.:
#NotNull(message="Field cannot be empty")
#Size(max = 6, message = "Field length is too long")
private String fieldOne;
I would like to be able to say that the user can only enter either fieldOne or fieldTwo, but not both. Is this possible through annotations or am I stuck to writing this validation in my code?

Edited:
To validate either one field has value but not other, I think you can use a custom validator at class level. Following is the idea:
1.
Create interface for your annotation:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ChoiceValidator.class)
#Documented
public #interface Choice {
String[] fields();
String message() default "{Choice.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2.
Create a implementation of ConstraintValidator to check the value to be validated is in fields inside Choice annotation:
public class ChoiceValidator
implements ConstraintValidator<Choice, Object> {
private List<String> fields;
#Override
public void initialize(final Choice choice) {
fields = Arrays.asList(choice.fields());
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext ctx) {
int nonNullFieldCount = 0;
for (String field : fields) {
try {
final String fieldValue = BeanUtils.getProperty(value, field);
if (fieldValue != null) {
nonNullFieldCount++;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
return nonNullFieldCount == 1;
}
}
After that, you can use it something like:
#Choice(fields= {"fieldOne", "fieldTwo"})
public class Foo {
String fieldOne;
String fieldTwo;
}
Original:
I am not sure I really get you mean, but looks like you want a validation on the Class types of a Object field. If there is the case, you may try to create your custom annotation and ConstraintValidator to do so. Following is the idea:
1.
Create interface for your annotation:
public #interface Choice {
Class<?>[] types();
}
2.
Create a implementation of ConstraintValidator to check the value to be validated is in types inside Choice annotation:
public class ChoiceValidator implements ConstraintValidator<Choice, Object> {
private List<Class<?>> clazzes;
#Override
public void initialize(Choice choice) {
clazzes = Arrays.asList(choice.types());
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
for (Class<?> clazz : clazzes) {
if (value.getClass().equals(clazz)) {
return true;
}
}
return false;
}
}
After that, you can use it something like:
#Choice(types = {FieldOneType.class, FieldTwoType.class})
public class Foo {
Object someType;
}
Hope this can help.

Related

Validation Exception - the right way to handle

I am trying to validate the postal code in my but the approach i am thinking of is not working out and I can't understand why.
I created a Validator, that hast to throw a ValidationException if it's not valid.
#Service
public class ZipCodeValidator{
public void validate(String zipCode){
validateNotEmpty(zipCode);
validateHasNoSpaces(zipCode);
}
private void validateNotEmpty(String zipCode){
if (zipCode.length() != 0){
throw new ValidationException("Postal Code can't be empty");
}
}
private void validateHasNoSpaces(String zipCode) {
if (zipCode.contains(" ")){
throw new ValidationException("Postal Code can't contain spaces");
}
}
}
In my service, where i receive the postal code I want to throw my custom exception (to which i pass the error message) like this:
try{
validator.validate(zipCode);
}catch (ValidationException ex){
throw new ZipCodeValidationException(ex.getMessage());
}
However it doesn't seem to work, that exception is not caught so the program runs further.
What am I doing wrong?
Is the whole approach wrong? What would you recommend?
Here's my custom Exception
public class ZipCodeValidationException extends Exception{
public ZipCodeValidationException(String message) {
super(message);
}
}
I recommend the following:
to process all exceptions in universal place as ExceptionHandler class, for more details see: https://www.baeldung.com/exception-handling-for-rest-with-spring
you can extend ValidationException from RuntimeException, that approach will allow unloading the code from try-catch constructions
#Service annotation is not quite right for converters or validators, as rule #Service class contains business logic, for helpers classes will be better use #Component, in total no differences between these two annotations only understanding which layer of application that component has
Please share the code for more suggestions and help.
Hi Please find my answer in 2 steps, first the correct and then the second the suggested way to implement.
Correction:
Please use ObjectUtils.isEmpty(arg) for checking if string is 0 length or null. Here is the modified version of your code
public interface ZipcodeService {
void validate(#Zipcode String zipCode) throws ZipCodeValidationException;
}
#Service
public static class ZipcodeServiceImpl implements ZipcodeService {
private final ZipCodeRegexMatcher zipCodeRegexMatcher;
public ZipcodeServiceImpl() {
zipCodeRegexMatcher = new ZipCodeRegexMatcher();
}
#Override
public void validate(String zipCode) throws ZipCodeValidationException {
// uncomment for Regex Validation
// boolean valid = zipCodeRegexMatcher.isValid(zipCode);
// uncomment for Simple text validation
final boolean valid = !ObjectUtils.isEmpty(zipCode);
if (!valid) {
throw new ZipCodeValidationException("Invalid zipcode");
}
}
}
This is how the caller looks like from Controller
#GetMapping(path = "dummy")
public String getDummy(#RequestParam("zipcode") String zipCode) {
try {
zipcodeService.validate(zipCode);
return zipCode;
} catch (ZipCodeValidationException e) {
return e.getMessage();
}
}
Suggested way:
add following entry to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Create Annotation and Validator as given below
#Constraint(validatedBy = {ZipcodeValidator.class})
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface Zipcode {
String message() default "Invalid Zipcode value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class ZipcodeValidator implements ConstraintValidator<Zipcode, String> {
private final ZipCodeRegexMatcher zipCodeRegexMatcher;
public ZipcodeValidator() {
zipCodeRegexMatcher = new ZipCodeRegexMatcher();
}
#Override
public boolean isValid(String zipCode, ConstraintValidatorContext constraintValidatorContext) {
return zipCodeRegexMatcher.isValid(zipCode);
}
}
Once this setup is done, head over to Controller class and annotated class with
#Validated and field you want to have validation on with the Custom Annotation i.e Zipcode we have just created. We are creating a Custom Validator in this case ZipcodeValidator by extending ConstraintValidator.
This is how the caller looks like:
#GetMapping
public String get(#Zipcode #RequestParam("zipcode") String zipCode) {
return zipCode;
}
On Failed validation, it throws javax.validation.ConstraintViolationException: get.zipCode: Invalid Zipcode value which you can customize according to your need by using ControllerAdvice.
You can also use #Zipcode annotation at the service level and it works the same way. Regarding ZipCodeRegexMatcher instead of creating it inside the constructor you can create a bean and inject that dependency. It is a simple class that has regex for zipcode and performs validation.
public static class ZipCodeRegexMatcher {
public static final String ZIP_REGEX = "^[0-9]{5}(?:-[0-9]{4})?$";
private final Pattern pattern;
public ZipCodeRegexMatcher() {
pattern = Pattern.compile(ZIP_REGEX);
}
public boolean isValid(String zipCode) {
return pattern.matcher(zipCode).matches();
}
}
The entire code is located here

How to apply size annotation for a field which can be either of two sizes

In my spring boot application I have a size validation on one of field in my dto. Now as per new requirement field size can be either 18 or 36. Earlier it was 36 so I had done like this:
#Size(min=36,max = 36,message = "id length should be 36")
Now as I have to validate against two sizes, Is there any way to do it with the annotation itself ?
Thanks,
Make a custom validator Annotation.
In CustomSize.java
#Target({ FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = CustomSizeValidator.class)
#Documented
public #interface CustomSize{
String message() default "{CustomSize.invalid}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
In CustomSizeValidator.java
class CustomSizeValidator implements ConstraintValidator<CustomSize, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
try {
if(value.length()==18 || value.length()==36){
return true;
}else{
return false;
}
} catch (Exception e) {
return false;
}
}
}
Use this in your POJO code.
#CustomSize
private String xyz;
OR
use #Pattern
#Pattern(regexp = "^(?:[A-Za-z0-9]{18}|[A-Za-z0-9]{36})$")
For Pattern see more here -> https://stackoverflow.com/a/34311990/1459174

Better way of using JSR303 annotations in Spring MVC to check whitespaces only

Im using a custom validation on a textbox that checks if entered sentence contains only white spaces
The code is as below
#Retention(value = RetentionPolicy.RUNTIME)
#javax.validation.Constraint(validatedBy = SmsMessageContent.SmsMessageContentValidator.class)
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Documented
public #interface SmsMessageContent {
public class SmsMessageContentValidator implements
ConstraintValidator<SmsMessageContent, String> {
#Override
public void initialize(final SmsMessageContent constraintAnnotation) {
}
#Override
public boolean isValid(final String value,
final ConstraintValidatorContext context) {
if (value == null)
return true;
try {
if (value.matches("^\\s*$")) {
context.buildConstraintViolationWithTemplate("{message.sms.content.not.empty}")
.addConstraintViolation();
context.disableDefaultConstraintViolation();
return false;
}
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
The above annotation im using in another class as
public class SmsMessageForm {
#SmsMessageContent
private String smsChannelContent = "";
Is there any annotation in JSR303 or JSR330 that can be used directly on
private String smsChannelContent = "";
instead of writing another annotation like what I have used to check for a whitespaces only?
I believe #Pattern does just that
#Pattern("^\\s*$")
private String smsChannelContent = "";
The javadoc states
The annotated String must match the following regular expression.
So use the pattern you need. I wasn't sure if you wanted to check against whitespace or for whitespace.

Formatting (string-) array as part of bean-validation message

I created my first custom validation annotation with validator class as inner class (which i find quite well-arranged).
It looks like this:
#Target( { ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {OneOfGroupNotNull.Validator.class})
#Documented
public #interface OneOfGroupNotNull {
// custom annotation properties
String[] fields();
// required by JSR-303
String message() default "One of group must be not null. {fields}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
public class Validator implements ConstraintValidator<OneOfGroupNotNull, Object> {
private String[] fields;
#Override
public boolean isValid(Object bean, ConstraintValidatorContext cvc) {
int countNotNull = 0;
for (String field : fields) {
try {
String property = BeanUtils.getProperty(bean, field);
if (property != null) {
countNotNull++;
}
} catch (Exception ex) {
throw new RuntimeException("Validation for field " + field + " of type " + bean.getClass()+ " raised exception.", ex);
}
}
return countNotNull == 1;
}
#Override
public void initialize(OneOfGroupNotNull a) {
fields = a.fields();
}
}
}
A bean class that is annotated with this validator may look like this:
#OneOfGroupNotNull(fields = {"a", "b", "c"})
public interface MyBean {
String getA();
Rarity getB();
Boolean getC();
}
The problem is that I cannot find a way to format the string array "fields". It just takes the to string method which results in something like:
One of group must be not null. [Ljava.lang.String;#157d954
If you changed type of fields from String[] to String then message with field names will be shown correctly. To get field names in constraint just split() it by comma.
Another option is to generating custom message inside constraint, like this:
cvc.disableDefaultConstraintViolation();
cvc.buildConstraintViolationWithTemplate("error message")
.addNode("field name with error")
.addConstraintViolation();
Which implementation of Bean Validation are you using? If you are using Hibernate Validator 4.3 this should actually work. See also https://hibernate.onjira.com/browse/HV-506.
As a workaround, why not use a List? There the default toString is more sensible.

Jackson Mapper post-construct

I am using the Jackson ObjectMapper to deserialize some JSON into a Java class, which we'll call PlayerData. I would like to add a bit of logic to the PlayerData class to fix up some data after the fields have been loaded in. For example, some early JSON files used to use a "sex" flag instead of a "gender" falg, so if the sex flag is set but the gender flag is not set, I'd like to set the value of the gender field to be the value of the sex field.
Is there some sort of #PostConstruct or #AfterLoad annotation that I could affix to a method? Or perhaps an interface that I could implement? I didn't notice one in the documentation, but it seemed like an obvious feature.
Found this thru a link in the comments (credit: fedor.belov). This appears to allow you to run code post construct.
Adding a comment for people who end up here via
http://jira.codehaus.org/browse/JACKSON-645 or
http://jira.codehaus.org/browse/JACKSON-538 and are looking for a
method which is called after a deserializer completes. I was able to
achieve the desired effect by including an annotation and writing a
converter which uses the same class as input and output.
#JsonDeserialize(converter=MyClassSanitizer.class) // invoked after class is fully deserialized
public class MyClass {
public String field1;
}
import com.fasterxml.jackson.databind.util.StdConverter;
public class MyClassSanitizer extends StdConverter<MyClass,MyClass> {
#Override
public MyClass convert(MyClass var1) {
var1.field1 = munge(var1.field1);
return var1;
}
}
If you're not using the #JsonCreator, then Jackson will use the setter and getter methods to set the fields.
So if you define the following methods assuming that you have Sex and Gender enums:
#JsonProperty("sex")
public void setSex(final Sex sex) {
this.sex = sex;
if (gender == null) {
gender = (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
#JsonProperty("gender")
public void setGender(final Gender gender) {
this.gender = gender;
if (sex == null) {
sex = (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
}
it would work.
Update: You can find all of the annotations of Jackson library here.
Update2: Other solution:
class Example {
private final Sex sex;
private final Gender gender;
#JsonCreator
public Example(#JsonProperty("sex") final Sex sex) {
super();
this.sex = sex;
this.gender = getGenderBySex(sex)
}
#JsonFactory
public static Example createExample(#JsonProperty("gender") final Gender gender) {
return new Example(getSexByGender(gender));
}
private static Sex getSexByGender(final Gender) {
return (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
private static Gender getGenderBySex(final Sex) {
return (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
This is not supported out of the box, but you can easily create your #JsonPostDeserialize annotation for methods to be called after deserialization.
First, define the annotation:
/**
* Annotation for methods to be called directly after deserialization of the object.
*/
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonPostDeserialize {
}
Then, add the following registration and implementation code to your project:
public static void addPostDeserializeSupport(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDescription,
JsonDeserializer<?> originalDeserializer) {
return new CustomAnnotationsDeserializer(originalDeserializer, beanDescription);
}
});
objectMapper.registerModule(module);
}
/**
* Class implementing the functionality of the {#link JsonPostDeserialize} annotation.
*/
public class CustomAnnotationsDeserializer extends DelegatingDeserializer {
private final BeanDescription beanDescription;
public CustomAnnotationsDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
super(delegate);
this.beanDescription = beanDescription;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new CustomAnnotationsDeserializer(newDelegatee, beanDescription);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Object deserializedObject = super.deserialize(p, ctxt);
callPostDeserializeMethods(deserializedObject);
return deserializedObject;
}
private void callPostDeserializeMethods(Object deserializedObject) {
for (AnnotatedMethod method : beanDescription.getClassInfo().memberMethods()) {
if (method.hasAnnotation(JsonPostDeserialize.class)) {
try {
method.callOn(deserializedObject);
} catch (Exception e) {
throw new RuntimeException("Failed to call #JsonPostDeserialize annotated method in class "
+ beanDescription.getClassInfo().getName(), e);
}
}
}
}
}
Finally, modify your ObjectMapper instance with addPostDeserializeSupport, it will invoke all #JsonPostDeserialize annotated method of deserialized objects.
This is something that has actually been suggested couple of times earlier. So maybe filing an RFE would make sense; there are multiple ways in which this could work: obvious ones being ability to annotate type (#JsonPostProcess(Processor.class)) and ability to register post-processor through Module API (so that there's basically a callback when Jackson constructs deserializer, to let module specify post-processor to use if any). But perhaps there are even better ways to do this.

Categories