JSR-303 bean validation with Spring does not kick in - spring

I've configured a JSR-303 custom validator following what's given in the docs (http://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/html/validation.html), complete with LocalValidatorFactoryBean and Hibernate validator on the classpath. However, my validator just refuses to kick in. I've put up a dirt simple test project here (https://github.com/abhijitsarkar/java/tree/master/spring-jsr-303), along with a failing unit test. Should you decide to take a look, just clone it and run
gradlew clean test from the root directory.
I'm using Spring framework 4.0.2.RELEASE and Hibernate validator 5.0.3.Final.
Method under validation:
public Coffee serve(#ValidOrder(Coffee.Blend.class) final String blend) {
ValidOrder annotation:
#Documented
#Constraint(validatedBy = {OrderValidator.class})
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER})
#NotNull
public #interface ValidOrder {
OrderValidator validator:
public class OrderValidator implements ConstraintValidator<ValidOrder, String> {
Spring config:
#Configuration
#ComponentScan(basePackages = "name.abhijitsarkar.coffeehouse")
#EnableAspectJAutoProxy
public abstract class AppConfig {
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
Dependencies:
dependencies {
compile(
[group: 'javax.inject', name: 'javax.inject', version: injectApiVersion],
[group: 'javax.validation', name: 'validation-api', version: beanValidationApiVersion],
[group: 'javax.annotation', name: 'javax.annotation-api', version: annotationApiVersion],
[group: 'org.springframework', name: 'spring-beans', version: springVersion],
[group: 'org.springframework', name: 'spring-context', version: springVersion],
[group: 'org.springframework', name: 'spring-aop', version: springVersion],
[group: 'org.aspectj', name: 'aspectjrt', version: aspectjVersion]
)
runtime(
[group: 'org.hibernate', name: 'hibernate-validator', version: hibernateValidatorVersion],
[group: 'javax.el', name: 'javax.el-api', version: elVersion],
[group: 'org.glassfish.web', name: 'javax.el', version: glassfishElVersion],
[group: 'org.aspectj', name: 'aspectjweaver', version: aspectjVersion]
)

A MethodValidationPostProcessor needs to be configured in addition to the LocalValidatorFactoryBean.
The class to be validated must have a #Validated annotation on it else methods are NOT searched for inline constraint annotations.
#Configuration
#ComponentScan(basePackageClasses = {SpringPackageComponentScanMarker.class})
#EnableAspectJAutoProxy
public abstract class AppConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
final MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
#Bean
public LocalValidatorFactoryBean validator() {
final LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
return localValidatorFactoryBean;
}
}
...
#Service
#Validated
public class SpringBarista extends Barista {
The part of the reference manual that talks about integration with JSR-303 conveniently omits these 2 crucial points without which BV does not kick in. This just caused me 6 hours of debugging and hair tearing where I did everything the doc said but BV would simply not kick in. I finally had to debug through the Spring source code to understand this. There got to be an easier way and I can't be the only one who had this problem. Created a JIRA SPR-11473 for them to update the doc.

For spring to validation to kick in the blend argument needs a #Valid annotation in front of it.
Your approach might not work since parameter contraints are not supported by the JSR303.
Constraint annotations can target any of the following ElementTypes:
FIELD for constrained attributes
METHOD for constrained getters
TYPE for constrained beans
ANNOTATION_TYPE for constraints composing other constraints
http://beanvalidation.org/1.0/spec/#constraintsdefinitionimplementation

Related

How to enable request duration time in Swagger UI using Springdoc?

I am using the following dependencies for Swagger:
implementation group: 'io.swagger.core.v3', name: 'swagger-annotations', version: '2.2.7'
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.12'
My Swagger configuration code is as follows:
#Configuration
public class SwaggerConfig {
#Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("All Apis")
.pathsToMatch("/api/v0/**")
.build();
}
#Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Kaim Tutorial API")
.description("Kaim Tutorial All Apis.")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("SpringShop Wiki Documentation")
.url("https://springshop.wiki.github.org/docs"));
}
}
What change do I need to make to show every API request duration time in Swagger UI?
I have checked other documentations but they are either for Swagger 2.0 or something that does not use OpenAPI.

ReflectionUtil error in Component/Functional Test with Repository layer

I am trying to run a JUnit test on my Repository layer
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {JPAConfig.class, SpringBootMainApplication.class})
public class AddBookTest {
#Autowired
private BookRepository bookRepository;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#BeforeEach
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
#Test
#Sql({"/book-schema.sql", "/book-data.sql"})
void addBook() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.put("/users/")
.header("X-MC-CorrelationID", UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.content("{\"book\": \"LOTR\"}")
.andExpect(MockMvcResultMatchers.status().isNoContent())
.andDo(MockMvcResultHandlers.print());
}
private String requestBody() {
return "{\n" +
" \"book\": \"LOTR\",\n" +
"}";
}
}
I am getting the following error:
java.lang.NoSuchMethodError: 'org.springframework.util.ReflectionUtils$MethodFilter org.springframework.util.ReflectionUtils$MethodFilter.and(org.springframework.util.ReflectionUtils$MethodFilter)'
at org.springframework.test.context.junit.jupiter.SpringExtension
Version:
implementation group: "org.springframework.boot", name: "spring-boot-starter-web", version: "2.2.1.RELEASE"
implementation group: "org.springframework.boot", name: "spring-boot-starter-data-jpa", version: "2.2.1.RELEASE"
implementation group: "org.springframework.boot", name: "spring-boot-starter-actuator", version: "2.2.1.RELEASE"
testImplementation group: 'org.springframework', name: 'spring-test', version: "5.2.1.RELEASE"
implementation group: 'org.springframework', name: 'spring-context', version: "5.2.1.RELEASE"
In short, all my Spring Boot dependencies are on 2.2.1.RELEASE train while the Spring framework is 5.2.1.RELEASE. Are these versions compatible?
Its hard to say for sure what happens but is sounds like a clash of versions of spring that you have on your classpath.
This specific method "and" is pretty new see the javadoc. It explicitly states that the method is since version "5.3.2" which is pretty new.
So I suggest examining the dependency tree of jars in the test class path to understand whether you mix between a new and some older versions of Spring.

Spring Boot , Swagger - Unable to render this definition

Spring boot
In my build.gradle:
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java' // jar not work with JSP
//id 'war' // to use JSP
}
group = 'com.myproject'
version = '1.0.2'
sourceCompatibility = '1.8'
processResources {
filesMatching('application.yml') {
expand(project.properties)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.code.gson:gson:2.7'
implementation 'com.h2database:h2'
implementation 'javax.servlet:jstl:1.2'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml'
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
implementation('com.squareup.retrofit2:retrofit:2.4.0')
implementation('com.squareup.retrofit2:converter-gson:2.4.0')
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2'
}
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}
test {
useJUnitPlatform()
}
here application.yml
logging:
level:
org.hibernate.SQL: debug
# for Spring Actuator
management:
endpoints:
web:
exposure:
include: beans, env, info, health, metrics
server:
port: 8090
connection-timeout: 30000
spring:
application:
name: E-shop
version: ${version}
datasource:
url: jdbc:h2:file:./db/eshop.h2.db
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate.ddl-auto: update
thymeleaf:
cache: false
enabled: true
prefix: classpath:/templates/
suffix: .html
h2: # default db. Open web page to H2 db -> http://localhost:8090/h2-console
console:
enabled: true
http:
converters:
preferred-json-mapper: gson
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
Here my security config:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource; // get by Spring
#Override
public void configure(HttpSecurity http) throws Exception {
http
.headers().frameOptions().sameOrigin()
.and()
.authorizeRequests()
// Here, you are making the public directory on the classpath root available without authentication (e..g. for css files)
.antMatchers("/public/**", "/registration.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.successHandler((request, response, authentication) -> new DefaultRedirectStrategy().sendRedirect(request, response, "/index"))
.failureUrl("/login-error.html")
.permitAll()
.and()
.logout()
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.permitAll();
}
// login by user from db
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.usersByUsernameQuery("SELECT username, password, active FROM usr WHERE username=?")
.authoritiesByUsernameQuery("SELECT u.username, ur.role FROM usr u INNER JOIN user_roles ur ON u.id = ur.user_id WHERE u.username=?");
}
In my application:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Date;
#SpringBootApplication
#EnableAsync
#EnableSwagger2
public class EshopApplication {
Here my swagger config:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SwaggerFoxConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
But when I try to start Swagger on address:
http://127.0.0.1:8090/swagger-ui.html
I get error:
Unable to render this definition
The provided definition does not specify a valid version field.
Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0).
Add the following bean to your code:
#Configuration
#EnableSwagger2
public class SpringFoxConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
I was facing same problem. In my case, the problem was some mistake in serialization using jackson X gson.
What I did: I added a converter to gson in the Swagger config class.
#Bean
public GsonHttpMessageConverter gsonHttpMessageConverter() {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson());
return converter;
}
private Gson gson() {
final GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
return builder.create();
}
public class SpringfoxJsonToGsonAdapter implements JsonSerializer<Json> {
#Override
public JsonElement serialize(Json json, Type type, JsonSerializationContext context) {
return JsonParser.parseString(json.value());
}
}

How to build a SOAP WS with Apache CXF + Spring Boot in Gradle?

The assignment was simple: A SOAP web service implemented with spring boot, JDBC using Gradle.
After some time looking around the discovery was made that "Spring-WS" only works with a contract-first development style.
And we didn't want that, so we dig a little further and found out what we already know, we had to use Apache CXF for a Contract Last development style.
So off we went to search, code and test; but once the data access and facades were done we couldn’t figure out how to wire the Apache CXF WS with the Spring Boot service Façade.
So… how is it done?
This is more of a rhetorical question, because after looking around we could not find an example of Spring Boot & Apache CXF working seamlessly together, so for anyone who may be searching, here is a simple example.
First the dependencies used by the Gradle project
build.gradle file
buildscript {
ext {
springBootVersion = '2.0.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse-wtp'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'war'
group = 'com.telcel'
version = '0.0.1-RC'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
configurations {
providedRuntime
}
dependencies {
// Apache CXF
compile(group: 'org.apache.cxf', name: 'cxf-spring-boot-starter-jaxws', version: '3.1.15') {
exclude(module: 'spring-boot-starter-tomcat')
}
// JDBC support
compile('org.springframework.boot:spring-boot-starter-jdbc')
// embedded servlet container
compile group: 'org.springframework.boot', name: 'spring-boot-starter-undertow', version: '1.5.4.RELEASE'
runtime group: 'com.ibm.informix', name: 'jdbc', version: '4.10.10.0'
testCompile('org.springframework.boot:spring-boot-starter-test')
testRuntime group: 'com.ibm.informix', name: 'jdbc', version: '4.10.10.0'
}
Then, we need some basic things for the CXF config.
application.properties file:
cxf.path=/service
server.address=0.0.0.0
We needed Spring Boot to create a CXF Endpoint, and we also needed that Endpoint to use our Spring aware Facade... this is where the wiring magic happened.
WebServiceConfig.java
package com.telcel.validaserie;
import com.telcel.validaserie.ui.ValidaSerieEndpoint;
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.xml.ws.Endpoint;
#Configuration
public class WebServiceConfig {
#Autowired
private Bus bus;
#Autowired
private ValidaSerieEndpoint validaSerieEndpoint;
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(bus, validaSerieEndpoint);
endpoint.publish("/");
return endpoint;
}
}
Notice the autowired ValidaSerieEndpoint that goes as a parameter into the EndpointImpl constructor, that's the trick, plain simple.
Finally just a simple web service implementation exposed as a Spring Bean (notice the Spring #Service stereotype)
ValidaSerieEndpoint.class
package com.telcel.validaserie.ui;
import com.telcel.validaserie.servicios.ValidaSeriesFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
#Service
#WebService
public class ValidaSerieEndpoint {
#Autowired
private ValidaSeriesFacade validaSeriesFacade;
#WebMethod
public String validaTelefonoIccid(#WebParam(name = "iccid") String iccid) {
return validaSeriesFacade.validaTelefonoIccid(iccid);
}
#WebMethod
public String validaTelefonoImei(#WebParam(name = "imei") String imei) {
return validaSeriesFacade.validaTelefonoImei(imei);
}
#WebMethod
public int validaFacturaIccid(#WebParam(name = "iccid") String iccid, #WebParam(name = "fuerza-venta") String fuerzaVenta) {
return validaSeriesFacade.validaFacturaIccid(iccid, fuerzaVenta);
}
#WebMethod
public int validaFacturaImei(#WebParam(name = "imei") String imei, #WebParam(name = "fuerza-venta") String fuerzaVenta) {
return validaSeriesFacade.validaFacturaImei(imei, fuerzaVenta);
}
}
And that's it quite simple after you look at it... hope this helps.

New Functional Web Framework with jetty

I wanted to setup an example for New in Spring 5: Functial Web Framework
So I set up a RouteConfiguration:
#Configuration
public class RouteConfiguration {
#Autowired
private MyService myService;
#Bean
public RouterFunction<?> routerFunction() {
return route(
GET("/first")
, myService::getItemsFirst)
.and(route(
GET("/second")
, myService::getItemsSecond));
}
}
I started my application using jetty and at first it seemed to work... until I wanted to call one of my methods: localhost:8080/first and it returned a 404.
Did I define my route configuration wrong or why arent the routes accessible?
EDIT
With netty you need to provide a Server Configuration Like the following:
#Configuration
public class HttpServerConfiguration {
#Autowired
private Environment environment;
#Bean
public HttpServer httpServer(final RouterFunction<?> routerFunction) {
final HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
final ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
final HttpServer server = HttpServer.create("localhost", Integer.valueOf(this.environment.getProperty("server.port")));
server.newHandler(adapter);
return server;
}
}
But I could not find something like this for jetty.
EDIT 2
My Dependencies:
repositories {
mavenCentral()
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }
}
dependencyManagement {
dependencies {
dependency (group: 'org.springframework.cloud', name: 'spring-cloud-starter-consul-discovery', version: '2.0.0.M1')
dependencySet (group: 'org.hibernate', version: '5.2.8.Final') {
entry 'hibernate-core'
entry 'hibernate-entitymanager'
entry 'hibernate-spatial'
}
}
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-hateoas')
compile('org.springframework.boot:spring-boot-starter-jetty')
compile('org.springframework.boot:spring-boot-starter-webflux') {
exclude module: 'spring-boot-starter-reactor-netty'
}
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-autoconfigure')
compile('org.springframework.boot:spring-boot-actuator')
compile('org.springframework.cloud:spring-cloud-starter-consul')
compile('org.springframework.cloud:spring-cloud-starter-consul-discovery')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('junit:junit')
}
Spring-Boot Version: 2.0.0.M3
Reading the comments, it seems this was an issue with dependencies bringing spring-boot-starter-web; if it is present, a Spring MVC application is started by Spring Boot.
There's a way to explicitly tell Spring Boot the type of the application, in the main Application class:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(AgentApplication.class);
application.setWebApplicationType(WebApplicationType.REACT‌​IVE);
application.run(args);
}

Resources