Create #ToLowerCase annotation to convert String value to lower case - java

We have SpringBoot application.
For our pojo's we want to create a custom #ToLowerCase annotation which converts the field variable value to lower case.
Eg:
#Data
Employee {
private String name;
#ToLowerCase
private String emailId;
private String gender;
private String phoneNumber;
}
So my custom #ToLowerCase annotation should convert emailId to lower case.
We want to use this annotation on all kind of Pojos, whether it is rest request pojo or JPA entity pojo.
I have gone through posts on many forums but didn't get any appropriate solution for same.
Is it possible to create such annotation in Spring Boot? If yes then how?
Kindly help
Thanks

Create a custom converter: ToLowerCaseConverter.
public class ToLowerCaseConverter extends StdConverter<String, String> {
#Override
public String convert(String value) {
if (value == null){
return null;
}
return value.toLowerCase();
}
}
After create a new annotation: ToLowerCase. It works for both incoming and outgoing Strings (#JsonDeserialize/#JsonSerialize).
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonSerialize(converter = ToLowerCaseConverter.class)
#JsonDeserialize(converter = ToLowerCaseConverter.class)
public #interface ToLowerCase {
}
Finally, your example will work as intended:
#Data
Employee {
#ToLowerCase
private String emailId;
}

Related

Use Enum in JPA

I have enum fields in DB in lowercase and as per Java standard, I have to define them in Uppercase in my spring boot application. If I am using lowercase, enums, I am able to get the results, but trying to convert DB lowercase value to uppercase using Spring attribute converter which is not working. Could someone tell me where I am doing wrong?
AccountLimitType.java
public enum AccountLimitType {
NONE,
HARD,
SOFT;
}
ConvertToDBAccountLimitType.java
#Converter(autoApply = true)
public class ConvertToDBAccountLimitType implements AttributeConverter<AccountLimitType, String>{
#Override
public String convertToDatabaseColumn(AccountLimitType attribute) {
return attribute.toString().toLowerCase();
}
#Override
public AccountLimitType convertToEntityAttribute(String dbData) {
return AccountLimitType.valueOf(dbData.toUpperCase());
}
}
Field in Entity class:
#NotNull
#Convert(converter = ConvertToDBAccountLimitType.class)
#Enumerated(EnumType.STRING)
#Column(name="limit_type",columnDefinition = "ENUM('none','hard','soft') default 'none'")
private AccountLimitType accountLimitType;
This is the response I am getting:
Response in postman
Can you try without #Enumerated(EnumType.STRING)
Also make sure that its calling your converter and don't forget to add a null check there

Custom annotation not working with #JsonProperty

I've run into a situation where I need to validate a field inside an object conditionally. More specifically, I have one class PhoneType which contains two fields
#Getter
#Setter
public class PhoneType {
#JsonProperty("#CountryCode")
private String countryCode;
#JsonProperty("#Number")
private String number;
}
The class PhoneType is used in three places,
#Getter
#Setter
class PersonContact {
#JsonProperty("Mobile")
private PhoneType mobile;
#JsonProperty("WorkPhone")
private PhoneType workPhone;
#JsonProperty("OfficeFax")
private PhoneType officeFax;
}
However, with mobile, there should be an additional validation rule applied to the number field. The number must be a number with length of 10.
I have two possible solutions in mind:
Create a custom annotation to validate number for mobile
Validate number using Jackson's StdConverter
Here are the implementation of both solutions
public class ContactConverter extends StdConverter<PersonContact, PersonContact> {
#SneakyThrows
#Override
public PersonContact convert(PersonContact personContact) {
boolean validMobilePhone = Pattern.compile("\\d{10}")
.matcher(relatedPersonContact.getMobileNumber())
.matches();
if (BooleanUtils.isFalse(validMobilePhone)) {
var errorMessage = String.format(INVALID_MOBILE_NUMBER, personContact.getMobileNumber());
throw new JsonParseException(null, errorMessage);
}
return personContact;
}
}
Converter is used like this
#Getter
#Setter
#JsonDeserialize(converter = ContactConverter.class)
public class PersonContact {
#JsonProperty("#Email")
private String email;
#JsonProperty("WorkPhone")
private PhoneType workPhone;
#JsonProperty("Mobile")
private PhoneType mobile;
}
This is the code for custom annotation, however it's not working
#Retention(RUNTIME)
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Constraint(validatedBy = MobilePhoneNumberValidator.class)
#interface Phone {
String format() default "";
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
static class MobilePhoneNumberValidator implements ConstraintValidator<Phone, PhoneType> {
#Override
public void initialize(Phone constraintAnnotation) {
System.out.println("test");
}
#Override
public boolean isValid(PhoneType phoneType, ConstraintValidatorContext context) {
System.out.println("test again");
return false;
}
}
and this is how I use it
#Phone(format = "\\d{10}")
#JsonProperty("Mobile")
private PhoneType mobile;
However, the code inside the Validator is not executed.
I wonder if there is anything wrong with the custom annotation. This is SpringBoot 2.3.0, I can't think of any other reason why the custom annotation is not working.
Please help if you know there is a legit way handling dynamic annotation in Java, or you know why the above code isn't working, or you know a legit way of validating object's field just by name.
EDIT 1:
It seems like due to my poor way of explanation, there is misunderstanding.
https://www.baeldung.com/javax-validation-groups , this doesn't work in this case, the validation is applied only with the declaration of the PhoneType property in other classes (PersonContact)
I have two possible solutions, custom annotation and Jackson's converter.
I have successfully applied the converter but couldn't make the custom annotation work.
My custom annotation should be run after #JsonProperty, because it needs to have the field PhoneType mobile number to be deserialized.

QUARKUS - MicroProfile REST Client: add a custom, not mapped field

I am following this article https://quarkus.io/guides/rest-client to build a REST Client to parse the output from the restcountries.eu service.
Here the class holding the model:
public class Country {
public String name;
public String alpha2Code;
public String capital;
public List<Currency> currencies;
public static class Currency {
public String code;
public String name;
public String symbol;
}
}
Now, suppose I would like to add a custom fields such as timestamp, to record the instant when this object has been created. I imagine, I would go ahead and add another field like below:
public class Country {
public String name;
public String alpha2Code;
public String capital;
public List<Currency> currencies;
public Instant timestamp; //<--------- added attribute
[....]
My question is: how do I tell the client to populate that field? Normally, I would have done it in the constructor. However, I could not find docs that explain this part.
Thanks for your help
Simone
You can actually do this in the default constructor. Frameworks like JSONB or Jackson expect POJOs to have a default constructor. They will call it when they create an instance of Country.
Use the #JsonbTransient or #JsonIgnore annotations to mark that attribute of your POJO as ignorable in order to avoid the unmarshaller complaining about attributes that cannot be found in the response.
#Data
public class Country {
private String name;
private String alpha2Code;
private String capital;
private List<Currency> currencies;
#JsonbTransient // if you're using JSONB (default in Quarkus)
#JsonIgnore // if you're using Jackson
private Instant timestamp;
public Country() {
this.timestamp = Instant.now();
}
PS The #Data annotation is something you should consider using. Encapsulation is not a bad thing but creating getters/setters is tedious. But Project Lombok certainly helps here.

How to apply Single annotation on multiple variables?

I am rookie in Java Annotation and have been searching for applying single annotation on multiple variable simultaneously.
Code:
#Document(collection = "users")
public class User {
private ObjectId id;
#NotNull
private String email;
private String imageURL;
private String authToken;
private Date createdDate;
private Date updateDate;
private boolean isActivated;
private int credits;
.....getter/Setter Method
I want to apply #NotNull property on email, imageURL and authToken too. I can do it by writing #NotNull to each variable but not preferring. How to do it?
#NotNull annotation can be applied at element not at group of elements.
JavaDoc: The annotated element must not be null. Accepts any type.
If you really want to get away with boiler plate code, you can use frameworks like Lombok which can help you to certain extent.
Link : http://projectlombok.org/features/Data.html
OR you can use reflection to validate all the method.
for (Field f : obj.getClass().getDeclaredFields()) {
f.setAccessible(true); // optional
if (f.get(obj) == null) {
f.set(obj, getDefaultValueForType(f.getType()));
// OR throw error
}
}
Java does not support multiple annotation of this type. But you can write something like this
Create a class with annotated field.
Create setters and getters to access the field.
Create all your name,email field as instance of this class.
This way fields will implicitly annotated as NotNull.
public class NotNullString {
#NotNull
String str;
public void set(String str)
{
this.str = str;
}
public String get()
{
return this.str;
}
}
NotNullString name;
NotNullString email;

Dynamic property filter using Jackson 2

I am building a REST service platform in which we have to support following query pattern:
format=summary which means we have to deserialize only the POJO attributes annotated with our custom annotation #Summary
format=detail which means we have to deserialize only the POJO attributes annotated with our custom annotation #Detail
fields=prop1,prop2,prop3 which means we have to deserialize the POJO attributes provided in the query.
I am using Jackson 2 (v2.3.0) I tried followings:
Developed custom annotations (#Summary and #Detail)
Developed a JsonFilter (code shown below) and annotated #JsonFilter to my POJO classes.
Location.java
#JsonFilter("customFilter")
public class Location implements Serializable {
#Summary
#Detail
private String id;
#Summary
#Detail
private String name;
#Summary
#Detail
private Address address;
// ... getters n setters
Address.java
#JsonFilter("customFilter")
public class Address implements Serializable {
#Detail
private String addressLine1;
#Detail
private String addressLine2;
#Detail
private String addressLine3;
#Detail
#Summary
private String city;
#Summary
#Detail
private String postalCode;
// ... getters n setters
CustomFilter.java
public class CustomFilter extends SimpleBeanPropertyFilter {
#Override
protected boolean include(BeanPropertyWriter propertyWriter) {
if(logger.isDebugEnabled()) {
logger.debug("include(BeanPropertyWriter) method called..");
}
return this.deserialize(propertyWriter);
}
#Override
protected boolean include(PropertyWriter propertyWriter) {
if(logger.isDebugEnabled()) {
logger.debug("include(PropertyWriter) method called..");
}
return this.deserialize((BeanPropertyWriter) propertyWriter);
}
private boolean deserialize(final BeanPropertyWriter beanPropertyWriter) {
final String format = (String) AppContext.get("format");
if(StringUtils.isNotBlank(format)) {
return deserializeForAnnotation(format, beanPropertyWriter);
} else {
#SuppressWarnings("unchecked")
final Set<String> fieldNames = (Set<String>) AppContext.get("fieldNames");
if(null != fieldNames && !fieldNames.isEmpty()) {
final String serializedPropertyName = beanPropertyWriter.getSerializedName().getValue();
return fieldNames.contains(serializedPropertyName);
}
}
return false;
}
private boolean deserializeForAnnotation(final String format, final BeanPropertyWriter beanPropertyWriter) {
if(StringUtils.equalsIgnoreCase(format, "detail")) {
return (null != beanPropertyWriter.getAnnotation(Detail.class));
} else if(StringUtils.equalsIgnoreCase(format, "summary")) {
return (null != beanPropertyWriter.getAnnotation(Summary.class));
}
return false;
}
}
I am getting intended result with annotations, however my 3rd requirement to support property names to filter is not working.
Could someone help; if possible with some examples?
I wrote a library called Squiggly Filter, which selects fields based on a subset of the Facebook Graph API syntax. For example, to select the zipCode of the address field of the user object, you would use the query string ?fields=address{zipCode}. One of the advantages of Squiggly Filter is that as long as you have access to the ObjectMapper that renders the json, you do not to have to modify the code of any of your controller methods.
Assuming, you are using the servlet API, you can do the following:
1) Register a filter
<filter>
<filter-name>squigglyFilter</filter-name>
<filter-class>com.github.bohnman.squiggly.web.SquigglyRequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>squigglyFilter</filter-name>
<url-pattern>/**</url-pattern>
</filter-mapping>
2) Initialize the ObjectMapper
Squiggly.init(objectMapper, new RequestSquigglyContextProvider());
3) You can now filter your json
curl https://yourhost/path/to/endpoint?fields=field1,field2{nested1,nested2}
You can also select fields based on annotations as well.
More information on Squiggly Filter is available on github.
If you want to go down the route of having a custom object mappers for each set of fields then your best bet is to retain the created object mappers in a cache somewhere so that the next time a user requests the same fields the object mapper can be reused.
Your cache could be as simple as a Set<String,ObjectMapper>, with the key being the fields as passed in by the user.

Categories