How to validate a field based on other field value in bean (pojo) class in Spring Boot using annotations - spring-boot

I have created a request class having some fields with getters & setters. Now I want to validate each & every field. So with this validation I need to check if the value for field1 is A then fields2 should be mandatory and if value for field1 is B then field3 should be mandatory and field2 will be optional. Consider the below pojo class.
public class CreateADTSpaceRequestDTO implements Serializable{
private static final long serialVersionUID = 5654993652896223164L;
#NotEmpty(message = "taskUId cannot be null/empty")
#JsonProperty(value = "taskUId")
private String taskUId;
#NotEmpty(message = "clientName cannot be null/empty")
#JsonProperty(value = "clientName")
private String clientName;
#NotEmpty(message = "SpaceType cannot be null/empty")
#JsonProperty(value = "spaceType")
private String spaceType;
public String getTaskUId() {
return taskUId;
}
public void setTaskUId(String taskUId) {
this.taskUId = taskUId;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public String getSpaceType() {
return spaceType;
}
public void setSpaceType(String spaceType) {
this.spaceType = spaceType;
}
}
In the above class we have a field called clientName, so based on client name value I want to validate spaceType field.
For ex. if clientName = A then spaceType is mandatory and if clientName = B then spaceType is optional.
Please help me with your comments how we can have this kind of validation using annotations or using regex or any other way.

Related

JPA calling default constructor even during POST request

I didn't had a default constructor in my entity class in the beginning. Eventually found out that JPA requires a default constructor in entity class so I made one.
After adding the default constructor, even during post requests, JPA keeps calling default constructor which leads to incorrect initialisation of properties. For example, if you see the property called availableSeats, it is initialised to 100, but during post request only default constructor is called which leads to initialisation of availableSeats to 0.
This is extremely weird and I don't understand what am I doing wrong here.
#Entity
public class Flight {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotBlank(message = "Airline Name cannot be blank!")
private String airlineName;
#NotBlank(message = "From-Location cannot be blank!")
private String fromLocation;
#NotBlank(message = "To-Location cannot be blank!")
private String toLocation;
#NotBlank(message = "Airport Gate Number cannot be blank")
private String gateNumber;
// #NotBlank(message = "Boarding time cannot be blank")
private ZonedDateTime dateTimeZone;
private static final int INITIAL_SEAT_CAPACITY = 100;
private int availableSeats;
// constructor should not be able to set id
public Flight(Long id, String airlineName, String fromLocation, String toLocation, String gateNumber, ZonedDateTime dateTimeZone, int availableSeats) {
this.id = id;
this.airlineName = airlineName;
this.fromLocation = fromLocation;
this.toLocation = toLocation;
this.gateNumber = gateNumber;
this.dateTimeZone = dateTimeZone;
// setting up initial number of available seats
this.availableSeats = INITIAL_SEAT_CAPACITY;
}
public Flight(){
}
// getters and setters
}
Also adding FlightController.java code here
#RestController
#RequestMapping("/api/flights")
public class FlightController {
#Autowired
FlightService flightService;
#GetMapping(value = "/")
public ResponseEntity<List<Flight>> getAllFlights(){
return flightService.getAllFlights();
}
#PostMapping(value = "/")
public ResponseEntity<String> createFlight(#Valid #RequestBody Flight flight){
return flightService.createFlight(flight);
}
#GetMapping(value = "/{id}")
public ResponseEntity<Flight> getFlightById(#PathVariable Long id){
return flightService.getFlightById(id);
}
#DeleteMapping(value = "/{id}")
public ResponseEntity<String> deleteFlight(#PathVariable Long id){
return flightService.deleteFlight(id);
}
}
Spring's controller uses default(zero argument) constructor for object creation and then uses it's setter methods for setting the values in the object. You cannot expect for spring to use parameterized constructor.
So if you need to set some default values then do it in zero argument constructor.
As #grigouille pointed out in the comments, JPA only uses default constructor. Hence, availableSeats should have been initialised in the default constructor too.

How to validate objects that were deserialized with Jackson polymorphic deserialization

Given the following request:
/enterprises-api/{{version}}/enterprises?query={"searchType":"FOUNDERSEARCH","maxResult":"61",...}
OR
/enterprises-api/{{version}}/enterprises?query={"searchType":"NAMEORADDRESSSEARCH","maxResult":"61",...}
I have managed to deserialize query into either a FounderSearch or NameOrAddressSearch object based on the searchType property.
However, Javax validation is ignored because this isn't a toplevel object. How can I resolve this?
Top level object:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "searchType")
#JsonSubTypes({
#JsonSubTypes.Type(value = FounderSearch.class, name = "FOUNDERSEARCH"),
#JsonSubTypes.Type(value = NamerOrAddressSearch.class, name = "NAMEORADDRESSSEARCH")
})
public abstract class Query {
public abstract boolean isFounderSearch();
}
One of the sub-level objects:
#Data
public class FounderSearch extends QueryObject {
#Min(1) #Max(60)
private Integer maxResult = 20;
#Pattern(regexp = "\\d{11}")
private String personNumber;
private List<CodeType> functions;
private Boolean activeFunctions;
public boolean isFounderSearch() {
return true;
}
public SearchType getSearchType() {
return SearchType.FOUNDERSEARCH;
}
}
If you add #Valid annotation on nested objects as well it will work.
something like this, above all the fields that have validation constraints.
#Data
public class FounderSearch extends QueryObject {
#Min(1) #Max(60)
#Valid
private Integer maxResult = 20;
#Pattern(regexp = "\\d{11}")
#Valid
private String personNumber;
private List<CodeType> functions;
private Boolean activeFunctions;
public boolean isFounderSearch() {
return true;
}
public SearchType getSearchType() {
return SearchType.FOUNDERSEARCH;
}
}

marshall attributes inside XML elements with JAXB

I work with Spring JPA and have the following entity:
#Entity
#Table(name = Constants.ENTITY_TABLE_PREFIX + "ENTRY")
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name = "monObj_info")
public class EntryXML implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#XmlAttribute
private long id;
#Column(name = "ip_address", nullable = true)
#XmlElement
private String ip_address;
#Column(name = "network_element_name", nullable = false)
#XmlElement
private String network_element_name;
public EntryXML() {}
public EntryXML(long id, String ip_address, String network_element_name) {
super();
this.id = id;
this.ip_address = ip_address;
this.network_element_name = network_element_name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getIp_address() {
return ip_address;
}
public void setIp_address(String ip_address) {
this.ip_address = ip_address;
}
public String getNetwork_element_name() {
return network_element_name;
}
public void setNetwork_element_name(String network_element_name) {
this.network_element_name = network_element_name;
}
}
and the endpoint:
#RestController
public class EntryXMLEndpoint {
#Autowired
private IEntryXMLService service;
#RequestMapping(value = "/restxml", produces = { "application/xml" })
public EntryXML findEntries() {
EntryXML record = service.findById(1);
return record;
}
}
Now the requested response is:
<monObj_info id="1">
<atribute name="ip_address" value="xx.xxx.xxx.x"/>
<atribute name="network_element_name" value="xxxxxx"/>
</monObj_info>
Of course what I get is :
<monObj_info id="1">
<ip_address>xx.xxx.xxx.x</ip_address>
<network_element_name>xxxxxx</network_element_name>
</monObj_info>
I read similar posts , but the problem is I cannot create a List with the required elements inside my Entity Class, since it will not map with any column in the respective table, any suggestions?
You can achieve your goal in a straight-forward but somewhat hackish way.
Since you don't want the ip_address and network_element_name properties
to be marshalled and unmarshalled directly, you need to remove their #XmlElement annotation
and add #XmlTransient.
Instead, you want some <atribute name="..." value="..." /> elements marshalled and unmarshalled.
Therefore you need to add the following things to your EntryXML class:
an attributes property holding a list of attributes.
It is annotated with #XmlElement so that it will be part of XML marshalling and unmarshalling.
It is annotated with #Transient so that it will not be part of database persistence.
a simple helper class Attribute for holding name and value.
name and value are annotated with #XmlAttribute so that they will be part of XML marshalling and unmarshalling.
a Marshal Event Callback (beforeMarshal)
for doing the conversion from ip_address and network_element_name
to the attributes list.
an Unmarshal Event Callback (afterUnmarshal)
for doing the opposite conversion.
#XmlElement(name = "atribute")
#Transient // from package javax.persistence
private List<Attribute> attributes;
// there is no need for getAttributes and setAttributes methods
private static class Attribute {
#SuppressWarnings("unused") // called by the unmarshaller
Attribute() {
}
Attribute(String name, String value) {
this.name = name;
this.value = value;
}
#XmlAttribute
private String name;
#XmlAttribute
private String value;
}
#SuppressWarnings("unused") // this method is called only by the marshaller
private boolean beforeMarshal(Marshaller marshaller) {
attributes = new ArrayList<>();
attributes.add(new Attribute("ip_address", ip_address));
attributes.add(new Attribute("network_element_name", network_element_name));
return true;
}
#SuppressWarnings("unused") // this method is called only by the unmarshaller
private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
if (attributes != null) {
for (Attribute attribute : attributes) {
switch (attribute.name) {
case "ip_address":
ip_address = attribute.value;
break;
case "network_element_name":
network_element_name = attribute.value;
break;
}
}
}
}
Then the XML output will look like this:
<monObj_info id="1">
<atribute name="ip_address" value="xx.xxx.xxx.x"/>
<atribute name="network_element_name" value="xxxxxx"/>
</monObj_info>

Spring JPA - Ignore a field only in persistence

I have a field called password which can be received by endpoint. But it cannot be sent back in response or persisted in Database
The class is as follows -
public class ShortURL {
#Pattern(regexp="^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]")
private String url;
#Size(min=8,max=16)
#Transient
private String password = null;
private boolean isPasswordProtected = false;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isPasswordProtected() {
return isPasswordProtected;
}
public void setPasswordProtected(boolean isPasswordProtected) {
this.isPasswordProtected = isPasswordProtected;
}
public ShortURL(
#Pattern(regexp = "^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]") String url,
#Size(min = 8, max = 16) String password, boolean isPasswordProtected) {
super();
this.url = url;
this.password = password;
this.isPasswordProtected = isPasswordProtected;
}
#Transient works properly. But adding the #JsonIgnore after #Transient causes problems -
Type definition error: [simple type, class java.lang.String];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
No fallback setter/field defined for creator property 'password'"
How do I achieve my intentions?
Depends on your Jackson version.
Before version 1.9, you could add #JsonIgnore to the getter of password and add #JsonProperty to the setter of the password field.
Recent versions of Jackson provide READ_ONLY and WRITE_ONLY annotation arguments for #JsonProperty, something like this:
#JsonProperty(access = Access.READ_ONLY)
private String password;
Yes you can use #JsonIgnore to let jackson ignore it during sending the user response but. There are certain best practices you should follow.
Never expose the Entities directly to the endpoint instead its better to have a wrapper i.e DTO that translates your entity to the required response.
For eg. in your case
public class ShortURL {
#Pattern(regexp="^(https?|ftp|file)://[-a-zA-Z0-9+&##/%?=~_|!:,.;]*[-a-zA-Z0-9+&##/%=~_|]")
private String url;
#Size(min=8,max=16)
private String password;
private boolean isPasswordProtected;
}
//here is the dto in which you can create a parameterised constructor and
accordingly invoke it based on the fields you want to set.
public class ShortURLDTO {
private String url;
public ShortURLDTO(String url){
this.url=url
}
}

SpringBoot concatenate search parameters browser url

I am starting working with Spring Boot. My aim is to make a limited search retrieving data from a database. I want to add multiple parameters in the query of the url.
So far I was able using the seek: http://localhost:8080/wsr/search/, to get a full search of the data in the database. But what I want is delimit the search under several conditions adding parameters in the url in the browser as for instance:
http://localhost:8080/data/search/person?name=Will&address=Highstreet&country=UK
http://localhost:8080/data/search/person?name=Will&name=Angie
http://localhost:8080/data/search/person?name=Will&name=Angie&country=UK
The problem I found is that I can't find the way to work with more than one condition. The only thing I got to make it work, is:
http://localhost:8080/data/search/person?name=Will
I surfed the web but no results for this exact problem, too much information but impossible to find this.
The code I have is:
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "country")
private String country;
public Value() {
}
public Value(int id, String name, String address, String country) {
this.id = id;
this.name = name;
this.address = address;
this.country = country;
}
//all getters and setters
}
public class Implementation {
#Autowired
private DataBase dataBase;
public List<Value> findById(#PathVariable final int id) {
return dataBase.findById(id);
}
public List<Value> findByName(#PathVariable final String name) {
return dataBase.findByName(name);
}
public List<Value> findByAddress(#PathVariable final String address) {
return dataBase.findByAddress(address);
}
public List<Value> findByCountry(#PathVariable final String country) {
return dataBase.findByCountry(country);
}
}
//#Component
#RepositoryRestResource(collectionResourceRel = "person", path = "data")
public interface DataBase extends JpaRepository<Value, Integer>{
public List<Value> findAll();
#RestResource(path = "ids", rel = "findById")
public List<Value> findById(#Param("id") int id) throws ServiceException;
#RestResource(path = "name", rel = "findByName")
public List<Value> findByName(#Param("name") String name) throws ServiceException;
#RestResource(path = "address", rel = "findByAddress")
public List<Value> findByAddress(#Param("address") String address) throws ServiceException;
#RestResource(path = "country", rel = "findByCountry")
public List<Value> findByCountry(#Param("country") String country) throws ServiceException;
}
Hope you can help me putting me in the correct way of what should do or is wrong. If possible some code will also be highly appreciated.
Best regards
You can use #RequestParam("nameParameter")annotation to map all the parameters you want. Let's say you have url like :
http://localhost:8080/data/search/person?name=Will&country=UK
then you can have an api like:
...
#RequestMapping(value = "/person")
public String api(#RequestParam("name") String name, #RequestParam("country") String country)
...

Resources