spring boot dynamic object modelling - java

I am creating a RESTful API using spring boot. I have the requirement where I need to make a request to the resource
/user/notification
Notification resource will accept values in bodyrequest and send the notification to users.
#ResponseBody
#RequestMapping(value = "/user/notification", method = RequestMethod.POST)
public NotificationResponse sendNotification(#Valid #RequestBody NotificationRequest notificationRequest){
// here is code where I need to build right
// object of type text/file/link/map (please read full question below)
notificationService.send(notificationRequest.getUsername(), object);
}
It accepts: username and data for notification. Here is NotificationRequest class:
public class NotificationRequest {
#NotEmpty
private String username;
#NotEmpty
private String type;
private String title;
#NotEmpty
private String content;
private String url;
private String longitude;
private String latitude;
private String file_url;
//getters and setters
}
I have 4 types of notifications ie. text, link, map and file. And their attributes are these.
text
- type
- title
- content
link
- type
- title
- content
- url
map
- type
- title
- longitude
- latitude
file
- type
- title
- content
- file_url
I created 4 classes for these so I can create the right object, As you can see type and title are common attributes so I used inheritance.
public class NotificationBase {
private String type;
private String title;
//getters and setters here
}
And extended other 4 classes like this.
public class TextNotification extends NotificationBase {
private String content;
//getters and setters here
}
My question is, How I create my classes so that
if someone wants to send text notification I would able to get an object of TextNotification and if someone wants to send file notification that I would able to create FileNotification object?
Note: Please note I do not want to use gJson or Jackson to create JSON objects in this case.
Please do let me know if I need to add any more information here.

You can use jackson annotation for subtyping. In your case, you have the field type that will represent what is the type of the object that you expect.
So it's better to have an abstract NotificationRequest class only with needed fields:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = TextNotificationRequest.class, name = "text"),
#JsonSubTypes.Type(value = LinkNotificationRequest.class, name = "link")})
public abstract class NotificationRequest {
#NotEmpty
private String username;
#NotEmpty
private String type;
private String title;
public abstract NotificationResponse buildNotificationResponse();
}
And the implementation of each type that you need.
public class TextNotificationRequest extends NotificationRequest {
public String content;
public NotificationResponse buildNotificationResponse{
return new TextNotificationResponse(content);
}
}
Now you can just do something like this
#ResponseBody
#RequestMapping(value = "/user/notification", method = RequestMethod.POST)
public NotificationResponse sendNotification(#Valid #RequestBody NotificationRequest notificationRequest){
NotificationResponse response = notificationRequest.buildNotificationResponse();
notificationService.send(notificationRequest.getUsername(), response);
}
Here is some explanation. By setting these annotations your are saying to jackson which class to instantiate based on the value of type field. If type == "text" jackson will instantiate TextNotificationRequest and so on. And now you can use the power of polymorphism to create NotificationResponse and avoid if-else statements.
Also jackson allow subtyping based not only on the field value. You can read in more detailed info here #JsonTypeInfo

Related

SDN6 - Projection interfaces with Property Mapping

I am using spring data neo4j 6.1.3 and following is my use case code snippets
Domain Entity
#Data
#Node("DATspace")
public class DatSpace {
#Id #GeneratedValue
private Long neoId;
#Property("SUPtitle")
private String title;
private String SUPid;
}
Test class
#SpringBootTest
#EnableNeo4jRepositories(basePackages = "com.rahal.marvel")
public class ProjectionTest {
#Autowired
private Neo4jTemplate neo4jTemplate;
interface DATspaceProjection {
String getTitle();
String getSUPid();
}
#Test
public void test_projection(){
DatSpace d = neo4jTemplate.findOne("MATCH (s:DATspace {SUPid: $id}) RETURN s", Collections.singletonMap("id", "SPC_ML7"), DatSpace.class).get();
d.setTitle("title modified");
d.setSUPid("SUPid modified");
DATspaceProjection p = neo4jTemplate.saveAs(d, DATspaceProjection.class);
}
}
Ideally above saveAs function should modify both DATspace.SUPtitle and DATspace.SUPid. However it only modify SUPid but not SUPtitle. I presume it is due to property mapping (#Property) . Is this a bug or are there any workaround?
The provided #Property annotation does only have an impact on the annotated property (title) itself.
There is no knowledge right now that goes from the getTitle() method in the projection to the annotated title field in the domain class.
To be safe when modifying this use the explicit property name:
interface DATspaceProjection {
String getSUPtitle();
String getSUPid();
}
I created an issue for improvement https://github.com/spring-projects/spring-data-neo4j/issues/2371

Spring Java POST endpoint that can have a field be different data types

I'm working on a Java Spring Boot HTTP Service application. I currently have a POST endpoint that I have defined inside of a #RestController. This controller, called processRequest takes an object called Info with the #RequestBody annotation.
Right now, I have it setup where a user can send JSON based on the Info class that I defined like this:
//Sample JSON Payload
{
"name": "Bob",
"age": 26,
"hobby": biking
}
//Sample Object
#RequiredArgsConstructor
public class Info {
public final String name;
public final int age;
public final String hobby
}
What I want to do know is respond to the situation where one of the fields is sent as a different datatype. For example:
//JSON payload with different datatype for a field
{
"name": "Bob",
age: 26,
"hobby": ["biking", "hiking"] //This is supposed to be a string but it's an array.
}
Is it possible to keep the endpoint properties the same but handle different data types? Maybe I can create another class where the fields are different and spring will automatically create the one that matches the input? I'm curious for what the best approach to this problem would be.
In this particular example, where the hobby could either be a single value or multiple values, I would rely on the Jackson ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
This can be configured application-wide within application.properties:
spring.jackson.deserialization.accept-single-value-as-array=true
Or this can be enabled for a specific field:
#RequiredArgsConstructor
public class Info {
public final String name;
public final int age;
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
public final List<String> hobby
}
For more complex cases, Jackson recommends that you write a wrapper with a specific type field to provide a hint of which type it should deserialize. For example:
public class InfoWrapper {
private String type;
#JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = PersonInfo.class, name = "person")
})
private Info info;
}
public interface Info {}
#RequiredArgsConstructor
public class PersonInfo implements Info {
public final String name;
public final int age;
public final String hobby
}
So, if you want to send a JSON containing PersonInfo, you can use the following JSON:
{
"type": "person",
"info": {
"name": "Bob",
"age": 26,
"hobby": "biking"
}
}
If you need more advanced control over what you want to do, you can write a custom deserializer and apply it with the #JsonDeserialize annotation.
You can use JsonNode for the field which changes. As below:
public class Info {
public String name;
public int age;
public JsonNode hobby;
#Schema(description = "")
#Valid
#JsonIgnore
public List<String> getHobbies() {
// if hobby is array create Java object and return
// else if hobby is String create List, add the string to it and return it.
// or some other solution of your choice and requirement
}
}

Create #ToLowerCase annotation to convert String value to lower case

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;
}

Change attribute name dynamically Gson while Serialization

I've got MultivaluedMap of custom object types which I am serializing using Gson. One of my requirement is to change the name of one of the object based on the length of the string to some other name.
I know that we can use annotations #SerializedName but it provides option for only one name alternative whereas I'm looking for two names for the same attribute and use it dynamically when serializing based on the string length.
How can I accomplish this?
Here's the outline of my custom object types:
Toplevel Class:
public class CompleteData{
private String country,
private String appId,
Private String userId,
private List<RecipientInfo> recipients;
private CustomDocument document;
<<setters//getters>>
public CompleteData(String country, String appId, String userId, List<RecipientInfo> recipients, CustomDocument document){
this.country=country..
..
..
}
CustomDocument Class:
public class CustomDocument{
String name;
String pageCount;
public CustomDocument(String name, int pageCount){
this.name = name;
this.pageCount = pageCount;
}
RecipientInfo Class:
public class RecipientInfo{
#serializedName("fullName")
String name;
String phoneNum;
public RecipientInfo(String name, String phoneNum){
this.name = name;
this.phoneNum = phoneNum;
}
}
Now I create List<CompleteData> completeData = new ArrayList<>();
Gather all the necessary information and add it to a MultivaluedMap as there are duplicate keys involved:
MultiValuedMap(<String, List<CompleteData>)
Now while using Gson to serialize this object, I want to change the "name" attribute in RecipientInfo class to be able to change dynamically based on the string length as fullname if the length is (>10 and <15) and fullNamewithSalu if the length is >20
Should I create a new class all together for this small change or is there a way I can serialize this object using Gson dynamically ?
Please help!
Thank you!

How to hide fields with a condition in RESTful WS?

I have a class called Report that I need to share using RESTful WS.
once in full with all its attributes
once in only a reduced version
Normally I'd use something like #XmlTransient to hide the fields, but this would prevent the full version from working.
Is there any way to set a condition or to kind of pre-filter fields just before the output so that it doesn't affect other uses of the same class?
My Report class looks like this:
public class Report {
private String reportId;
private String title;
private String content;
private Date created;
private Date modified;
...
}
The RESTful sharing for the full Report looks like this:
#GET
#Path("/{reportId}")
public Report getReport(#PathParam("reportId") String reportId) {
return Mock.getReport(reportId);
}
The full output I need looks like this:
{
"reportId": "d83badf3",
"title": "The tales of lamaru",
"content": "When once the great Imgur started his journey...",
"created": 1519672434866,
"modified": 1519672434866
}
The short output I need should look like this:
{
"reportId": "d83badf3",
"title": "The tales of lamaru"
}
What is necessary to achieve this?
Why don't you use Inheritance?
Parent
public class Report {
private String reportId;
private String title;
}
Child
public class FullReport extends Report{
private String content;
private Date createdp;
private Date modified;
}
When you need full report set return type as FullReport otherwise Report
Jackson has two different annotations to use when you want to exclude some class members from the JSON serialization and deserialization processes. These two annotations are #JsonIgnore and #JsonIgnoreProperties.
#JsonIgnoreProperties is an annotation at the class level and it expects that the properties to be excluded would be explicitly indicated in the form of a list of strings.
#JsonIgnore instead is a member-level or method-level annotation, which expects that the properties to be excluded are marked one by one.
try this.
public class Report {
private String reportId;
private String title;
#JsonIgnore
private String content;
#JsonIgnore
private Date created;
#JsonIgnore
private Date modified;
...
}

Categories