#NotNull #NotBlank #Valid is not working in spring boot rest api validation - spring

#NotNull, #NotEmpty, #NotBlank annotations are not working in my rest controller. My requirement is to restrict the flow at controller and get 400 error when i hit the controller without required parameters. But when i pass null or empty headers to my controller, i am not getting 400 error. my controller hits my handler class which is not the expected behaviour
Below is my controller
#RestController
#RequestMapping("/intelligent-banking")
public class CrossSellOffersRetrievalController {
#Autowired
private CrossSellOffersRetrievalHandler crossSellOffersRetrievalHandler;
#Autowired
Environment env;
#GetMapping(value = "/cross-sell-offers/{interactionPoint}", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<CrossSellOffersRetrievalResponse> getApplicableOffers(
#RequestHeader(value = "channelId", required = true) #Valid String channelId,
#RequestHeader(value = "clientId", required = false) String clientId,
#RequestHeader(value = "actionId", required = true) #NotNull #NotEmpty String actionId,
#RequestHeader(value = "customerId", required = true) #NotNull #NotBlank String customerId,
#RequestHeader(value = "cinSuffix", required = true) #NotNull #NotBlank String cinSuffix,
#RequestHeader(value = "sessionId", required = true) #NotNull #NotBlank String sessionId,
#RequestHeader(value = "countryCode", required = true) #NotNull #NotBlank String countryCode,
#PathVariable(value = "interactionPoint", required = true) #NotNull #NotBlank String interactionPoint,
#RequestParam(value = "numberOfOffers", required = false) Integer numberOfOffers)
throws CrossSellOffersException {
try {
CrossSellOffersRetrievalResponse crossSellOffersResponse = crossSellOffersRetrievalHandler.getCrossSellOffersRetrievalResponse(channelId,
customerId, cinSuffix, countryCode, interactionPoint, sessionId, numberOfOffers);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("CustomerId", customerId);
return new ResponseEntity<>(crossSellOffersResponse, httpHeaders, HttpStatus.OK);
}
catch (Exception e) {
LOGGER.error("Inside CrossSellOffersRetrievalController::getApplicableOffers::Exception - Exception occurred at getApplicableOffers: {} ",e.getMessage());
throw new CrossSellOffersException(Constants.ERROR_CODE, e.getMessage());
}
}
}

You need to enable validation for both request parameters and path variables via adding #Validated annotation to your controller in order for validations to be executed.

Use this maven dependency inorder make those work
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>

was using This dependency of validation in spring boot and didn't work ,due to version upgrade of spring boot to 2.4.0
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
Replaced it with spring-boot-starter-validation and it worked .
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-
starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.4.0</version>
</dependency>
**Need to enable validation for both request parameters and path variables via adding #Validated annotation to your controller in order for validations to be executed.**

Related

Quarkus Reactive with Vert.x and Hibernate Reactive / java.lang.NullPointerException: Cannot store to object array because "this.loadedState" is null

i am trying to use quarkus reactive with vert.x and hibernate reactive.
this is my pom.xml:
<quarkus-plugin.version>1.12.2.Final</quarkus-plugin.version>
and
<quarkus.platform.version>1.12.2.Final</quarkus.platform.version>
with:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
this is my application.properties file:
# postgres-configuration
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=partner_usr
quarkus.datasource.password=postgrespw
quarkus.datasource.reactive.url=vertx-reactive:postgres://localhost:3310/partnerdb
# test, but not working (schema's won't created)
quarkus.hibernate-orm.database.generation.create-schemas=true
# working (drop-and-create only on mysql, not on postgres)
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.http.cors=true
Then, i have following entities:
#Data
#MappedSuperclass
public abstract class IdEntity {
#Id
#SequenceGenerator(name = "entitySeq", sequenceName = "entitiy_id", allocationSize = 1, initialValue = 5)
#GeneratedValue(generator = "entitySeq", strategy = GenerationType.AUTO)
private Long id;
}
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class Person extends IdEntity {
private String firstName;
private String lastName;
public Person() {
}
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Address personAddress;
}
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class Address extends IdEntity {
private String street;
private String houseNumber;
private int postalCode;
private String city;
#OneToMany(orphanRemoval = true, mappedBy = "personAddress", fetch = FetchType.LAZY)
private List<Person> persons = new ArrayList<>();
public Address() {
}
}
Now, i am calling a reactive web-service with a reactive db access:
#Path("/person")
#ApplicationScoped
public class PersonResource {
#Inject
io.vertx.mutiny.pgclient.PgPool sqlClient;
#Inject
Mutiny.Session mutinySession;
#GET
//#Produces(MediaType.APPLICATION_JSON)
#Path("/list-persons")
#Route(path = "/list-persons", methods = HttpMethod.GET, produces = MediaType.APPLICATION_JSON)
#Transactional
public Multi<Person> listAllPersons() {
// return sqlClient.query("SELECT * FROM Person ORDER BY lastName ASC").execute()
// .onItem().transformToMulti(set -> Multi.createFrom().iterable(set))
// .onItem().transform(this::transformPersons);
return mutinySession.createQuery("SELECT f FROM Person f ORDER BY f.lastName")
.getResults().onItem().transform(this::transformObject);
}
private Person transformObject(Object f) {
return (Person)f;
}
private List<Object> transformPersons(Object f) {
final Person person = (PartnerMockEntity)f;
final List<Object> bogus = new ArrayList<>();
bogus.add(partner);
return bogus;
}
}
Exception:
Resulted in: com.fasterxml.jackson.databind.JsonMappingException: Cannot store to object array because "this.loadedState" is null (through reference chain: de.subito.model.Person["personAddress"]->de.subito.model.Address["person"])
I tried to use :
FetchType.EAGER on Address in Person
I removed the #OneToMany Relation in Address: this solves the error (yay), but the addresses won't be returned in the resulting json (id is existing, but the values are not fetched)
The questions is, how can i fetch in reactive those kind of relations without getting errors?
Or do i need a angular page in order to display this correctly?
Somehow i forgot about how fetchType.Lazy works.
Simply add a join fetch into the hql and everything works as expected.
SELECT p from Person p left join fetch p.personAddress
When using this query, there's no session/closed or any other exception thrown and the json result will be displayed as expected.
Additional note: in order to avoid recursive serialization, it is required to use the
#JsonManagedReference and #JsonBackReference
Annotations, depending on your needs to your relations.

Spring Boot testing with H2 - Table "OAUTH_ACCESS_TOKEN" not found

I'm thinking I need to setup the db testing environment (e.g. create tables, seed users so that token can be issued with credentials) before I can run tests but not sure how to.
#RunWith(SpringRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
public class UsersControllerTest {
// ...
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(springSecurityFilterChain).build();
}
private String obtainAccessToken(String username, String password) throws Exception {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("client_id", CLIENTID);
params.add("grant_type", CLIENTPASSWORD);
params.add("username", username);
params.add("password", password);
ResultActions result = mockMvc.perform(post("/oauth/token")
.params(params)
.with(httpBasic(CLIENTID, CLIENTPASSWORD))
.accept("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"));
String resultString = result.andReturn().getResponse().getContentAsString();
JacksonJsonParser jsonParser = new JacksonJsonParser();
return jsonParser.parseMap(resultString).get("access_token").toString();
}
#Test
public void givenNoToken_whenGetAllUsers_thenUnauthorized() throws Exception {
mockMvc.perform(
get("/users")
).andExpect(status().isUnauthorized());
}
#Test
public void givenToken_whenGetAllUsers_thenOk() throws Exception {
String accessToken = obtainAccessToken("martyn", "secret");
mockMvc.perform(
get("/users")
.header("Authorization", "Bearer " + accessToken)
).andExpect(status().isOk());
}
// ...
Here is a typical Entity for this app:
#Entity(name = "users")
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8507204786382662588L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String surname;
#Column(nullable = false, unique = true)
private String email;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false)
#JsonIgnore
private String password;
#OneToMany
#JoinColumn(name="user_id") // cascade = CascadeType.ALL, orphanRemoval = true
private List<Fund> funds;
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// standard getters and setters
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Fund> getFunds() {
return funds;
}
}
But also, as the error indicates, I'd need to generate these oauth* tables too.
Here is my src/test/resources/application.properties
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
So I guess I want to generate the tables (entities, and oauth*) in the H2 database prior to running tests and populate with a single user(?) but can't seem to figure how this is done in Spring Boot. Or should I not be hitting any database and mocking JDBC altogether? Could someone point me in the correct direction as to how to prepare a test environment here? I'm at a bit of a loss.
UPDATE
Here is how dataSource is configured:
#Configuration
public class JDBCTokenConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;
#Value("${spring.datasource.username}")
private String dbUsername;
#Value("${spring.datasource.password}")
private String dbPassword;
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(datasourceUrl);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
return dataSource;
}
#Bean
public TokenStore tokenStore(DataSource dataSource) {
return new JdbcTokenStore(dataSource);
}
// #Bean
// public TokenStore tokenStore() {
// return new InMemoryTokenStore();
// }
}
pom.xml
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
...
</dependencies>
I think it is a good thing to reach your in memory database without mocking. Honestly, you will need more time to configure rather than creating the correct schema needed for your database.
Using Spring-boot, it is very easy to configure to test your application:
Declare using spring-boot-starter-jpa
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Add the in memory DB for your tests
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
Delete your spring.datasource properties in application.properties
Thanks to #SpringBootApplication, the datasource will be automatically configured to connect to your H2 in memory database.
Create the SQL schema
By default, spring-boot-starter-jpa configures automatically the datasource to execute scripts classpath:/schema.sql and if you need also, classpath:/data.sql.
Create a schema.sql in src/test/resources, and create the tables (copy the following content, I think this is what you need: https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql) (I am not sure for this, maybe hibernate creates your schema on his own).
Create your schema in src/test/resources/schema.sql and seed the users in src/test/resources/data.sql
Check also the spring documentation to know how you can configure hibernate :
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#data-properties
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference
Now I understand, which is everybody, that you need to have your configuration different dependending on your environment. The best to do that is to rely on profiles.
In your situation, you have a prod profile, and a test profile.
Declare the profile and keep your spring.datasource properties in your src/test/resources/application.properties (the easier in your case I think)
I suggest you to read this Configure specific in memory database for testing purpose in Spring, and let me know if you have troubles configuring your environment.
With this, you will need to:
Add an annotation at the top of your Test class #ActiveProfiles('test')
Restore the spring.datasource properties you previously deleted an put them in src/test/resources/application-test.properties
Let me know

swagger springfox - bean validation JSR 303 not recognize

I followed this tutorial https://springframework.guru/spring-boot-restful-api-documentation-with-swagger-2/ to generate a swagger documentation.
It's working but when I try to add some validation in my bean I don't find the information in the documentation:
#ApiOperation(value = "Creates a product",
notes="Populates a product instance bla bla")
#RequestMapping(value = "/add", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity saveProduct( #Valid #RequestBody Product product){
productService.saveProduct(product);
return new ResponseEntity("Product saved successfully", HttpStatus.OK);
}
My entity with the validations annotations :
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
// #ApiModelProperty(notes = "The database generated product ID")
private Integer id;
#Version
// #ApiModelProperty(notes = "The auto-generated version of the product")
#NotNull
private Integer version;
// #ApiModelProperty(notes = "The application-specific product ID" )
private String productId;
// #ApiModelProperty(notes = "The product description")
#NotBlank
#Size(max = 50)
private String description;
// #ApiModelProperty(notes = "The image URL of the product")
private String imageUrl;
// #ApiModelProperty(notes = "The price of the product", required = true)
#NotNull
private BigDecimal price;
But when I check the documentation I don't have those validation information:
Here https://github.com/springfox/springfox/issues/987 they say that we need to update our dependencies and it's what I did :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
Did I miss something in the configuration? Any idea to help me ?
I found the solution in this post : http://vojtechruzicka.com/documenting-spring-boot-rest-api-swagger-springfox/.
All is explained :
Unfortunately, JSR-303 based documentation does not work out of the box, you need an additional dependency:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-bean-validators</artifactId>
<version>2.8.0</version>
</dependency>
And you need to import BeanValidatorPluginsConfiguration configuration file on top of your swagger configuration class:
#Configuration
#EnableSwagger2
#Import(BeanValidatorPluginsConfiguration.class)
public class SpringFoxConfig {
...
}
Thank you #vojtech-ruzicka https://stackoverflow.com/users/4560142/vojtech-ruzicka

Spring Boot validation of RequestBody Dto annotated in Rest API

In my controller I have annotated the request parameter with the #Valid annotation and the field of my DTO with the #NotNull annotation, but the validation doesn't seem to work.
Are there any configurations to do in order to proceed with the validation? Following there are the Controller and the DTO class details.
#RepositoryRestController
#RequestMapping(value = "/download_pdf")
public class PurchaseController {
#Autowired
private IPurchaseService iPurchaseService;
#Loggable
#RequestMapping(value = "view_order", method = RequestMethod.POST)
public ResponseEntity getPDF(#RequestBody #Valid CustomerOfferDto offer,
HttpServletResponse response) {
return iPurchaseService.purchase(offer, response);
}
}
public class CustomerOfferDto {
#NotNull
private String agentCode;
// getter and setter...
}
Following are the steps I did to make it work.
Add dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Constraints in DTO class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#ValidTaskDTO
public class TaskDTO {
#FutureOrPresent
#NotNull(message = "DueDate must not be null")
private ZonedDateTime dueDate;
#NotBlank(message = "Title cannot be null or blank")
private String title;
private String description;
#NotNull
private RecurrenceType recurrenceType;
#Future
#NotNull(message = "RepeatUntil date must not be null")
private ZonedDateTime repeatUntil;
}
RestController method with #Valid annotation on requestBody argument:
#RestController
#RequestMapping("/tasks")
#Validated
public class TaskController {
#PostMapping
public TaskDTO createTask(#Valid #RequestBody TaskDTO taskDTO) {
.....
}
}
On making a POST request with requestbody containing null value for dueDate, I got the expected error message as shown below.
{
"timestamp": "2021-01-20T11:38:53.043232",
"status": 400,
"error": "Bad Request",
"message": "DueDate must not be null"
}
I hope this helps. For details on class level constraints, hav a look at this video.
In my projects, this usually happens when I change my code from lets say Entity to DTO and forget to add #ModelAttribute to my DTO parameter.
If this also happened to you, try adding #ModelAttribute("offer") to your DTO parameter.

cassandra-driver-mapping: InvalidTypeException: Invalid 32-bits float value, expecting 4 bytes but got 6

As I got issues with spring-data-cassandra with docker as describer here I switched to use com.datastax.cassandra library for cassandra operations but I am getting issues while mapping the resulted object using entity mapper as per this link
Here is my code ...
public String getUser(String userName) {
Mapper<User> mapper = manager.mapper(User.class);
User result = mapper.get(userName); // no issue getting User
String accountNum = result.getAccountId();
return accountNum ;
}
public Account getAccount(String accountNum){
Mapper<Account> mapper = manager.mapper(Account.class);
Account account = mapper.get(accountNum); // getting error here
return account;
}
Account.java
#Table(name = "account")
public class Account {
#PartitionKey
private String accountNum;
private String accountsubtype;
private float currentbalance;
private String customertype;
private String firstname;
private boolean isactive;
private String payments;
private String status;
....
//Getters & Setters
}
pom.xml dependecies
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-mapping</artifactId>
<version>3.0.0</version>
</dependency>

Resources