Add a validator based on the Bean Validation API

Resolves BATCH-2690
This commit is contained in:
Mahmoud Ben Hassine
2018-06-26 16:21:09 +02:00
parent 84d349ea9d
commit 9f2b752f3f
8 changed files with 444 additions and 1 deletions

View File

@@ -72,6 +72,7 @@ allprojects {
h2databaseVersion = '1.4.196'
hibernateVersion = '5.2.12.Final'
hibernateValidatorVersion = '6.0.4.Final'
javaxElVersion = '3.0.0'
hsqldbVersion = '2.4.0'
jackson2Version = '2.9.2'
gsonVersion = '2.8.5'
@@ -322,7 +323,8 @@ project('spring-batch-infrastructure') {
testRuntime "com.sun.mail:javax.mail:$javaMailVersion"
testRuntime "org.codehaus.groovy:groovy-jsr223:$groovyVersion"
testRuntime "org.jruby:jruby:$jrubyVersion"
testRuntime "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion"
testRuntime "org.glassfish:javax.el:$javaxElVersion"
testRuntime "org.beanshell:bsh:$beanshellVersion"
optional "javax.jms:javax.jms-api:$jmsVersion"
@@ -604,6 +606,8 @@ project('spring-batch-samples') {
testCompile "org.apache.activemq:activemq-kahadb-store:$activemqVersion"
testRuntime "com.sun.mail:javax.mail:$javaMailVersion"
testRuntime "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion"
testRuntime "org.glassfish:javax.el:$javaxElVersion"
provided "mysql:mysql-connector-java:$mysqlVersion"
provided "com.h2database:h2:$h2databaseVersion"

View File

@@ -2659,6 +2659,45 @@ public SpringValidator validator() {
}
----
You can also use the `BeanValidatingItemProcessor` to validate items annotated with
the Bean Validation API (JSR-303) annotations. For example, given the following type `Person`:
[source, java]
----
class Person {
@NotEmpty
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
----
you can validate items by declaring a `BeanValidatingItemProcessor` bean in your
application context and register it as a processor in your chunk-oriented step:
[source, java]
----
@Bean
public BeanValidatingItemProcessor<Person> beanValidatingItemProcessor() throws Exception {
BeanValidatingItemProcessor<Person> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
beanValidatingItemProcessor.setFilter(true);
return beanValidatingItemProcessor;
}
----
[[process-indicator]]
=== Preventing State Persistence

View File

@@ -11,6 +11,7 @@ The Spring Batch 4.1 release adds the following features:
* A new `@SpringBatchTest` annotation to simplify testing batch components
* A new `@EnableBatchIntegration` annotation to simplify remote chunking configuration
* A new `JsonItemReader` and `JsonFileItemWriter` to support the JSON format
* Add support for validating items with the Bean Validation API
[[whatsNewTesting]]
=== `@SpringBatchTest` Annotation
@@ -160,3 +161,47 @@ Writing JSON data is also supported through the `JsonFileItemWriter`.
For more details about JSON support, see the
<<readersAndWriters.adoc#jsonReadingWriting,ItemReaders and ItemWriters>> chapter.
[[whatsNewBeanValidationApi]]
=== Bean Validation API support
This release brings a new `ValidatingItemProcessor` implementation called
`BeanValidatingItemProcessor` which allows you to validate items annotated with
the Bean Validation API (JSR-303) annotations. For example, given the following
type `Person`:
[source, java]
----
class Person {
@NotEmpty
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
----
you can validate items by declaring a `BeanValidatingItemProcessor` bean in your
application context and register it as a processor in your chunk-oriented step:
[source, java]
----
@Bean
public BeanValidatingItemProcessor<Person> beanValidatingItemProcessor() throws Exception {
BeanValidatingItemProcessor<Person> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
beanValidatingItemProcessor.setFilter(true);
return beanValidatingItemProcessor;
}
----

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.validator;
import javax.validation.Validator;
import org.apache.shiro.util.Assert;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
/**
* A {@link ValidatingItemProcessor} that uses the Bean Validation API (JSR-303)
* to validate items.
*
* @param <T> type of items to validate
* @author Mahmoud Ben Hassine
* @since 4.1
*/
public class BeanValidatingItemProcessor<T> extends ValidatingItemProcessor<T> {
private Validator validator;
/**
* Create a new instance of {@link BeanValidatingItemProcessor} with the
* default configuration.
*/
public BeanValidatingItemProcessor() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.afterPropertiesSet();
this.validator = localValidatorFactoryBean.getValidator();
}
/**
* Create a new instance of {@link BeanValidatingItemProcessor}.
* @param localValidatorFactoryBean used to configure the Bean Validation validator
*/
public BeanValidatingItemProcessor(LocalValidatorFactoryBean localValidatorFactoryBean) {
Assert.notNull(localValidatorFactoryBean, "localValidatorFactoryBean must not be null");
this.validator = localValidatorFactoryBean.getValidator();
}
@Override
public void afterPropertiesSet() throws Exception {
SpringValidatorAdapter springValidatorAdapter = new SpringValidatorAdapter(this.validator);
SpringValidator<T> springValidator = new SpringValidator<>();
springValidator.setValidator(springValidatorAdapter);
springValidator.afterPropertiesSet();
setValidator(springValidator);
super.afterPropertiesSet();
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.validator;
import javax.validation.constraints.NotEmpty;
import org.junit.Assert;
import org.junit.Test;
/**
* @author Mahmoud Ben Hassine
*/
public class BeanValidatingItemProcessorTest {
@Test
public void testValidObjectValidation() throws Exception {
// given
BeanValidatingItemProcessor<Foo> validatingItemProcessor = new BeanValidatingItemProcessor<>();
validatingItemProcessor.afterPropertiesSet();
Foo foo = new Foo("foo");
// when
Foo processed = validatingItemProcessor.process(foo);
// then
Assert.assertNotNull(processed);
}
@Test(expected = ValidationException.class)
public void testInvalidObjectValidation() throws Exception {
// given
BeanValidatingItemProcessor<Foo> validatingItemProcessor = new BeanValidatingItemProcessor<>();
validatingItemProcessor.afterPropertiesSet();
Foo foo = new Foo("");
// when
validatingItemProcessor.process(foo);
// then
// expected exception
}
private static class Foo {
@NotEmpty
private String name;
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.sample.validation;
import java.util.Arrays;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.item.support.ListItemWriter;
import org.springframework.batch.item.validator.BeanValidatingItemProcessor;
import org.springframework.batch.sample.validation.domain.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Mahmoud Ben Hassine
*/
@Configuration
@EnableBatchProcessing
public class ValidationSampleConfiguration {
@Autowired
private JobBuilderFactory jobs;
@Autowired
private StepBuilderFactory steps;
@Bean
public ListItemReader<Person> itemReader() {
Person person1 = new Person(1, "foo");
Person person2 = new Person(2, "");
return new ListItemReader<>(Arrays.asList(person1, person2));
}
@Bean
public ListItemWriter<Person> itemWriter() {
return new ListItemWriter<>();
}
@Bean
public BeanValidatingItemProcessor<Person> itemValidator() throws Exception {
BeanValidatingItemProcessor<Person> validator = new BeanValidatingItemProcessor<>();
validator.setFilter(true);
validator.afterPropertiesSet();
return validator;
}
@Bean
public Step step() throws Exception {
return this.steps.get("step")
.<Person, Person>chunk(1)
.reader(itemReader())
.processor(itemValidator())
.writer(itemWriter())
.build();
}
@Bean
public Job job() throws Exception {
return this.jobs.get("job")
.start(step())
.build();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.sample.validation.domain;
import javax.validation.constraints.NotEmpty;
/**
* @author Mahmoud Ben Hassine
*/
public class Person {
private int id;
@NotEmpty
private String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.sample.validation;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.item.support.ListItemWriter;
import org.springframework.batch.sample.validation.domain.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Mahmoud Ben Hassine
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {ValidationSampleConfiguration.class})
public class ValidationSampleFunctionalTests {
@Autowired
private Job job;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private ListItemWriter<Person> listItemWriter;
@Test
public void testItemValidation() throws Exception {
// given
JobParameters jobParameters = new JobParameters();
// when
JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters);
// then
Assert.assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode());
List<? extends Person> writtenItems = this.listItemWriter.getWrittenItems();
Assert.assertEquals(1, writtenItems.size());
Assert.assertEquals("foo", writtenItems.get(0).getName());
}
}