Merge spring-batch-core-tests in spring-batch-core

This commit is contained in:
Mahmoud Ben Hassine
2020-12-04 16:03:58 +01:00
parent bf612650ea
commit 236ee88dcf
72 changed files with 1602 additions and 2125 deletions

View File

@@ -29,7 +29,7 @@ import org.springframework.batch.core.job.flow.FlowJob;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.test.namespace.config.DummyNamespaceHandler;
import org.springframework.batch.core.test.namespace.config.DummyNamespaceHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test;
import javax.sql.DataSource;
import org.junit.Before;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
/**
* @author Mahmoud Ben Hassine
*/
public class AbstractIntegrationTests {
protected DataSource dataSource;
@Before
public void setUp() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.addScript(new ClassPathResource("/org/springframework/batch/core/schema-drop-hsqldb.sql"));
databasePopulator.addScript(new ClassPathResource("/org/springframework/batch/core/schema-hsqldb.sql"));
databasePopulator.addScript(new ClassPathResource("/business-schema-hsqldb.sql"));
databasePopulator.execute(this.dataSource);
}
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright 2015-2019 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
*
* https://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.core.test.concurrent;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
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.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jdbc.datasource.embedded.ConnectionProperties;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.lang.Nullable;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ClassUtils;
import static org.junit.Assert.assertEquals;
/**
* @author Michael Minella
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ConcurrentTransactionTests.ConcurrentJobConfiguration.class)
public class ConcurrentTransactionTests {
@Autowired
private Job concurrentJob;
@Autowired
private JobLauncher jobLauncher;
@DirtiesContext
@Test
public void testConcurrentLongRunningJobExecutions() throws Exception {
JobExecution jobExecution = jobLauncher.run(concurrentJob, new JobParameters());
assertEquals(jobExecution.getStatus(), BatchStatus.COMPLETED);
}
@Configuration
@EnableBatchProcessing
public static class ConcurrentJobConfiguration extends DefaultBatchConfigurer {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor();
}
/**
* This datasource configuration configures the HSQLDB instance using MVCC. When
* configured using the default behavior, transaction serialization errors are
* thrown (default configuration example below).
*
* return new PooledEmbeddedDataSource(new EmbeddedDatabaseBuilder().
* addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql").
* addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql").
* build());
* @return
*/
@Bean
DataSource dataSource() {
ResourceLoader defaultResourceLoader = new DefaultResourceLoader();
EmbeddedDatabaseFactory embeddedDatabaseFactory = new EmbeddedDatabaseFactory();
embeddedDatabaseFactory.setDatabaseConfigurer(new EmbeddedDatabaseConfigurer() {
@Override
@SuppressWarnings("unchecked")
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
try {
properties.setDriverClass((Class<? extends Driver>) ClassUtils.forName("org.hsqldb.jdbcDriver", this.getClass().getClassLoader()));
}
catch (Exception e) {
e.printStackTrace();
}
properties.setUrl("jdbc:hsqldb:mem:" + databaseName + ";hsqldb.tx=mvcc");
properties.setUsername("sa");
properties.setPassword("");
}
@Override
public void shutdown(DataSource dataSource, String databaseName) {
try {
Connection connection = dataSource.getConnection();
Statement stmt = connection.createStatement();
stmt.execute("SHUTDOWN");
}
catch (SQLException ex) {
}
}
});
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.addScript(defaultResourceLoader.getResource("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql"));
databasePopulator.addScript(defaultResourceLoader.getResource("classpath:org/springframework/batch/core/schema-hsqldb.sql"));
embeddedDatabaseFactory.setDatabasePopulator(databasePopulator);
return embeddedDatabaseFactory.getDatabase();
}
@Bean
public Flow flow() {
return new FlowBuilder<Flow>("flow")
.start(stepBuilderFactory.get("flow.step1")
.tasklet(new Tasklet() {
@Nullable
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
return RepeatStatus.FINISHED;
}
}).build()
).next(stepBuilderFactory.get("flow.step2")
.tasklet(new Tasklet() {
@Nullable
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
return RepeatStatus.FINISHED;
}
}).build()
).build();
}
@Bean
public Step firstStep() {
return stepBuilderFactory.get("firstStep")
.tasklet(new Tasklet() {
@Nullable
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println(">> Beginning concurrent job test");
return RepeatStatus.FINISHED;
}
}).build();
}
@Bean
public Step lastStep() {
return stepBuilderFactory.get("lastStep")
.tasklet(new Tasklet() {
@Nullable
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println(">> Ending concurrent job test");
return RepeatStatus.FINISHED;
}
}).build();
}
@Bean
public Job concurrentJob() {
Flow splitFlow = new FlowBuilder<Flow>("splitflow").split(new SimpleAsyncTaskExecutor()).add(flow(), flow(), flow(), flow(), flow(), flow(), flow()).build();
return jobBuilderFactory.get("concurrentJob")
.start(firstStep())
.next(stepBuilderFactory.get("splitFlowStep")
.flow(splitFlow)
.build())
.next(lastStep())
.build();
}
@Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource());
factory.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED");
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return factory.getObject();
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test.football;
import static org.junit.Assert.assertEquals;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.test.AbstractIntegrationTests;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Dave Syer
* @author Mahmoud Ben Hassine
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/simple-job-launcher-context.xml", "/META-INF/batch/footballJob.xml"})
public class FootballJobIntegrationTests extends AbstractIntegrationTests {
/** Logger */
private final Log logger = LogFactory.getLog(getClass());
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Test
public void testLaunchJob() throws Exception {
JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().addLong("commit.interval", 10L)
.toJobParameters());
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
for (StepExecution stepExecution : execution.getStepExecutions()) {
logger.info("Processed: " + stepExecution);
if (stepExecution.getStepName().equals("playerload")) {
// The effect of the retries
assertEquals(new Double(Math.ceil(stepExecution.getReadCount() / 10. + 1)).intValue(),
stepExecution.getCommitCount());
}
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test.football;
import static org.junit.Assert.assertEquals;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.test.AbstractIntegrationTests;
import org.springframework.batch.support.DatabaseType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Dave Syer
* @author Mahmoud Ben Hassine
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/simple-job-launcher-context.xml", "/META-INF/batch/footballSkipJob.xml"})
public class FootballJobSkipIntegrationTests extends AbstractIntegrationTests {
/** Logger */
private final Log logger = LogFactory.getLog(getClass());
private JdbcTemplate jdbcTemplate;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
private DatabaseType databaseType;
@Autowired
public void setDataSource(DataSource dataSource) throws Exception {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
databaseType = DatabaseType.fromMetaData(dataSource);
}
@Test
public void testLaunchJob() throws Exception {
try {
if (databaseType == DatabaseType.POSTGRES || databaseType == DatabaseType.ORACLE) {
// Extra special test for these platforms (would have failed
// the job with UNKNOWN status in Batch 2.0):
jdbcTemplate.update("SET CONSTRAINTS ALL DEFERRED");
}
}
catch (Exception e) {
// Ignore (wrong platform)
}
JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().addLong("skip.limit", 0L)
.toJobParameters());
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
for (StepExecution stepExecution : execution.getStepExecutions()) {
logger.info("Processed: " + stepExecution);
}
// They all skip on the second execution because of a primary key
// violation
long retryLimit = 2L;
execution = jobLauncher.run(job,
new JobParametersBuilder().addLong("skip.limit", 100000L).addLong("retry.limit", retryLimit)
.toJobParameters());
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
for (StepExecution stepExecution : execution.getStepExecutions()) {
logger.info("Processed: " + stepExecution);
if (stepExecution.getStepName().equals("playerload")) {
// The effect of the retries is to increase the number of
// rollbacks
int commitInterval = stepExecution.getReadCount() / (stepExecution.getCommitCount() - 1);
// Account for the extra empty commit if the read count is
// commensurate with the commit interval
int effectiveCommitCount = stepExecution.getReadCount() % commitInterval == 0 ? stepExecution
.getCommitCount() - 1 : stepExecution.getCommitCount();
long expectedRollbacks = Math.max(1, retryLimit) * effectiveCommitCount + stepExecution.getReadCount();
assertEquals(expectedRollbacks, stepExecution.getRollbackCount());
assertEquals(stepExecution.getReadCount(), stepExecution.getWriteSkipCount());
}
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2006-2009 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
*
* https://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.core.test.football;
import static org.junit.Assert.assertEquals;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.jdbc.JdbcTestUtils;
/**
* @author Dave Syer
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/simple-job-launcher-context.xml", "/META-INF/batch/parallelJob.xml"})
public class ParallelJobIntegrationTests {
/** Logger */
private final Log logger = LogFactory.getLog(getClass());
@Autowired
private JobLauncher jobLauncher;
private JdbcTemplate jdbcTemplate;
@Autowired
private Job job;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Before
public void clear() {
JdbcTestUtils.deleteFromTables(jdbcTemplate, "PLAYER_SUMMARY", "GAMES", "PLAYERS");
}
@Test
public void testLaunchJob() throws Exception {
JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().toJobParameters());
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
for (StepExecution stepExecution : execution.getStepExecutions()) {
logger.info("Processed: "+stepExecution);
}
}
}

View File

@@ -0,0 +1,296 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.domain;
import java.io.Serializable;
@SuppressWarnings("serial")
public class Game implements Serializable {
private String id;
private int year;
private String team;
private int week;
private String opponent;
private int completes;
private int attempts;
private int passingYards;
private int passingTd;
private int interceptions;
private int rushes;
private int rushYards;
private int receptions;
private int receptionYards;
private int totalTd;
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @return the year
*/
public int getYear() {
return year;
}
/**
* @return the team
*/
public String getTeam() {
return team;
}
/**
* @return the week
*/
public int getWeek() {
return week;
}
/**
* @return the opponent
*/
public String getOpponent() {
return opponent;
}
/**
* @return the completes
*/
public int getCompletes() {
return completes;
}
/**
* @return the attempts
*/
public int getAttempts() {
return attempts;
}
/**
* @return the passingYards
*/
public int getPassingYards() {
return passingYards;
}
/**
* @return the passingTd
*/
public int getPassingTd() {
return passingTd;
}
/**
* @return the interceptions
*/
public int getInterceptions() {
return interceptions;
}
/**
* @return the rushes
*/
public int getRushes() {
return rushes;
}
/**
* @return the rushYards
*/
public int getRushYards() {
return rushYards;
}
/**
* @return the receptions
*/
public int getReceptions() {
return receptions;
}
/**
* @return the receptionYards
*/
public int getReceptionYards() {
return receptionYards;
}
/**
* @return the totalTd
*/
public int getTotalTd() {
return totalTd;
}
/**
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* @param year the year to set
*/
public void setYear(int year) {
this.year = year;
}
/**
* @param team the team to set
*/
public void setTeam(String team) {
this.team = team;
}
/**
* @param week the week to set
*/
public void setWeek(int week) {
this.week = week;
}
/**
* @param opponent the opponent to set
*/
public void setOpponent(String opponent) {
this.opponent = opponent;
}
/**
* @param completes the completes to set
*/
public void setCompletes(int completes) {
this.completes = completes;
}
/**
* @param attempts the attempts to set
*/
public void setAttempts(int attempts) {
this.attempts = attempts;
}
/**
* @param passingYards the passingYards to set
*/
public void setPassingYards(int passingYards) {
this.passingYards = passingYards;
}
/**
* @param passingTd the passingTd to set
*/
public void setPassingTd(int passingTd) {
this.passingTd = passingTd;
}
/**
* @param interceptions the interceptions to set
*/
public void setInterceptions(int interceptions) {
this.interceptions = interceptions;
}
/**
* @param rushes the rushes to set
*/
public void setRushes(int rushes) {
this.rushes = rushes;
}
/**
* @param rushYards the rushYards to set
*/
public void setRushYards(int rushYards) {
this.rushYards = rushYards;
}
/**
* @param receptions the receptions to set
*/
public void setReceptions(int receptions) {
this.receptions = receptions;
}
/**
* @param receptionYards the receptionYards to set
*/
public void setReceptionYards(int receptionYards) {
this.receptionYards = receptionYards;
}
/**
* @param totalTd the totalTd to set
*/
public void setTotalTd(int totalTd) {
this.totalTd = totalTd;
}
@Override
public String toString() {
return "Game: ID=" + id + " " + team + " vs. " + opponent + " - " + year;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Game other = (Game) obj;
if (id == null) {
if (other.id != null)
return false;
}
else if (!id.equals(other.id))
return false;
return true;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.domain;
import java.io.Serializable;
@SuppressWarnings("serial")
public class Player implements Serializable {
private String id;
private String lastName;
private String firstName;
private String position;
private int birthYear;
private int debutYear;
@Override
public String toString() {
return "PLAYER:id=" + id + ",Last Name=" + lastName +
",First Name=" + firstName + ",Position=" + position +
",Birth Year=" + birthYear + ",DebutYear=" +
debutYear;
}
public String getId() {
return id;
}
public String getLastName() {
return lastName;
}
public String getFirstName() {
return firstName;
}
public String getPosition() {
return position;
}
public int getBirthYear() {
return birthYear;
}
public int getDebutYear() {
return debutYear;
}
public void setId(String id) {
this.id = id;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setPosition(String position) {
this.position = position;
}
public void setBirthYear(int birthYear) {
this.birthYear = birthYear;
}
public void setDebutYear(int debutYear) {
this.debutYear = debutYear;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.domain;
/**
* Interface for writing {@link Player} objects to arbitrary output.
*/
public interface PlayerDao {
void savePlayer(Player player);
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.domain;
/**
* Domain object representing the summary of a given Player's
* year.
*
* @author Lucas Ward
*
*/
public class PlayerSummary {
private String id;
private int year;
private int completes;
private int attempts;
private int passingYards;
private int passingTd;
private int interceptions;
private int rushes;
private int rushYards;
private int receptions;
private int receptionYards;
private int totalTd;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getCompletes() {
return completes;
}
public void setCompletes(int completes) {
this.completes = completes;
}
public int getAttempts() {
return attempts;
}
public void setAttempts(int attempts) {
this.attempts = attempts;
}
public int getPassingYards() {
return passingYards;
}
public void setPassingYards(int passingYards) {
this.passingYards = passingYards;
}
public int getPassingTd() {
return passingTd;
}
public void setPassingTd(int passingTd) {
this.passingTd = passingTd;
}
public int getInterceptions() {
return interceptions;
}
public void setInterceptions(int interceptions) {
this.interceptions = interceptions;
}
public int getRushes() {
return rushes;
}
public void setRushes(int rushes) {
this.rushes = rushes;
}
public int getRushYards() {
return rushYards;
}
public void setRushYards(int rushYards) {
this.rushYards = rushYards;
}
public int getReceptions() {
return receptions;
}
public void setReceptions(int receptions) {
this.receptions = receptions;
}
public int getReceptionYards() {
return receptionYards;
}
public void setReceptionYards(int receptionYards) {
this.receptionYards = receptionYards;
}
public int getTotalTd() {
return totalTd;
}
public void setTotalTd(int totalTd) {
this.totalTd = totalTd;
}
@Override
public String toString() {
return "Player Summary: ID=" + id + " Year=" + year + "[" + completes + ";" + attempts + ";" + passingYards +
";" + passingTd + ";" + interceptions + ";" + rushes + ";" + rushYards + ";" + receptions +
";" + receptionYards + ";" + totalTd;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PlayerSummary other = (PlayerSummary) obj;
if (id == null) {
if (other.id != null)
return false;
}
else if (!id.equals(other.id))
return false;
return true;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.internal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.exception.ExceptionHandler;
public class FootballExceptionHandler implements ExceptionHandler {
private static final Log logger = LogFactory
.getLog(FootballExceptionHandler.class);
@Override
public void handleException(RepeatContext context, Throwable throwable)
throws Throwable {
if (!(throwable instanceof NumberFormatException)) {
throw throwable;
} else {
logger.error("Number Format Exception!", throwable);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.internal;
import org.springframework.batch.core.test.football.domain.Game;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
public class GameFieldSetMapper implements FieldSetMapper<Game> {
@Override
public Game mapFieldSet(FieldSet fs) {
if(fs == null){
return null;
}
Game game = new Game();
game.setId(fs.readString("id"));
game.setYear(fs.readInt("year"));
game.setTeam(fs.readString("team"));
game.setWeek(fs.readInt("week"));
game.setOpponent(fs.readString("opponent"));
game.setCompletes(fs.readInt("completes"));
game.setAttempts(fs.readInt("attempts"));
game.setPassingYards(fs.readInt("passingYards"));
game.setPassingTd(fs.readInt("passingTd"));
game.setInterceptions(fs.readInt("interceptions"));
game.setRushes(fs.readInt("rushes"));
game.setRushYards(fs.readInt("rushYards"));
game.setReceptions(fs.readInt("receptions", 0));
game.setReceptionYards(fs.readInt("receptionYards"));
game.setTotalTd(fs.readInt("totalTd"));
return game;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.internal;
import java.util.List;
import org.springframework.batch.core.test.football.domain.Game;
import org.springframework.batch.item.ItemWriter;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
public class JdbcGameDao extends JdbcDaoSupport implements ItemWriter<Game> {
private SimpleJdbcInsert insertGame;
@Override
protected void initDao() throws Exception {
super.initDao();
insertGame = new SimpleJdbcInsert(getDataSource()).withTableName("GAMES").usingColumns("player_id", "year_no",
"team", "week", "opponent", " completes", "attempts", "passing_yards", "passing_td", "interceptions",
"rushes", "rush_yards", "receptions", "receptions_yards", "total_td");
}
@Override
public void write(List<? extends Game> games) {
for (Game game : games) {
SqlParameterSource values = new MapSqlParameterSource().addValue("player_id", game.getId()).addValue(
"year_no", game.getYear()).addValue("team", game.getTeam()).addValue("week", game.getWeek())
.addValue("opponent", game.getOpponent()).addValue("completes", game.getCompletes()).addValue(
"attempts", game.getAttempts()).addValue("passing_yards", game.getPassingYards()).addValue(
"passing_td", game.getPassingTd()).addValue("interceptions", game.getInterceptions())
.addValue("rushes", game.getRushes()).addValue("rush_yards", game.getRushYards()).addValue(
"receptions", game.getReceptions()).addValue("receptions_yards", game.getReceptionYards())
.addValue("total_td", game.getTotalTd());
this.insertGame.execute(values);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.internal;
import org.springframework.batch.core.test.football.domain.Player;
import org.springframework.batch.core.test.football.domain.PlayerDao;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import javax.sql.DataSource;
/**
* @author Lucas Ward
*
*/
public class JdbcPlayerDao implements PlayerDao {
public static final String INSERT_PLAYER =
"INSERT into PLAYERS (player_id, last_name, first_name, pos, year_of_birth, year_drafted)" +
" values (:id, :lastName, :firstName, :position, :birthYear, :debutYear)";
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public void savePlayer(Player player) {
namedParameterJdbcTemplate.update(INSERT_PLAYER, new BeanPropertySqlParameterSource(player));
}
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.internal;
import java.util.List;
import org.springframework.batch.core.test.football.domain.PlayerSummary;
import org.springframework.batch.item.ItemWriter;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import javax.sql.DataSource;
public class JdbcPlayerSummaryDao implements ItemWriter<PlayerSummary> {
private static final String INSERT_SUMMARY = "INSERT into PLAYER_SUMMARY(ID, YEAR_NO, COMPLETES, ATTEMPTS, PASSING_YARDS, PASSING_TD, "
+ "INTERCEPTIONS, RUSHES, RUSH_YARDS, RECEPTIONS, RECEPTIONS_YARDS, TOTAL_TD) "
+ "values(:id, :year, :completes, :attempts, :passingYards, :passingTd, "
+ ":interceptions, :rushes, :rushYards, :receptions, :receptionYards, :totalTd)";
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public void write(List<? extends PlayerSummary> summaries) {
for (PlayerSummary summary : summaries) {
MapSqlParameterSource args = new MapSqlParameterSource().addValue("id", summary.getId()).addValue("year",
summary.getYear()).addValue("completes", summary.getCompletes()).addValue("attempts",
summary.getAttempts()).addValue("passingYards", summary.getPassingYards()).addValue("passingTd",
summary.getPassingTd()).addValue("interceptions", summary.getInterceptions()).addValue("rushes",
summary.getRushes()).addValue("rushYards", summary.getRushYards()).addValue("receptions",
summary.getReceptions()).addValue("receptionYards", summary.getReceptionYards()).addValue(
"totalTd", summary.getTotalTd());
namedParameterJdbcTemplate.update(INSERT_SUMMARY, args);
}
}
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.internal;
import org.springframework.batch.core.test.football.domain.Player;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
public class PlayerFieldSetMapper implements FieldSetMapper<Player> {
@Override
public Player mapFieldSet(FieldSet fs) {
if(fs == null){
return null;
}
Player player = new Player();
player.setId(fs.readString("ID"));
player.setLastName(fs.readString("lastName"));
player.setFirstName(fs.readString("firstName"));
player.setPosition(fs.readString("position"));
player.setDebutYear(fs.readInt("debutYear"));
player.setBirthYear(fs.readInt("birthYear"));
return player;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2006-2007 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
*
* https://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.core.test.football.internal;
import java.util.List;
import org.springframework.batch.core.test.football.domain.Player;
import org.springframework.batch.core.test.football.domain.PlayerDao;
import org.springframework.batch.item.ItemWriter;
public class PlayerItemWriter implements ItemWriter<Player> {
private PlayerDao playerDao;
@Override
public void write(List<? extends Player> players) throws Exception {
for (Player player : players) {
playerDao.savePlayer(player);
}
}
public void setPlayerDao(PlayerDao playerDao) {
this.playerDao = playerDao;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test.football.internal;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.batch.core.test.football.domain.PlayerSummary;
import org.springframework.jdbc.core.RowMapper;
/**
* RowMapper used to map a ResultSet to a {@link org.springframework.batch.core.test.football.domain.PlayerSummary}
*
* @author Lucas Ward
* @author Mahmoud Ben Hassine
*
*/
public class PlayerSummaryMapper implements RowMapper<PlayerSummary> {
/* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public PlayerSummary mapRow(ResultSet rs, int rowNum) throws SQLException {
PlayerSummary summary = new PlayerSummary();
summary.setId(rs.getString(1));
summary.setYear(rs.getInt(2));
summary.setCompletes(rs.getInt(3));
summary.setAttempts(rs.getInt(4));
summary.setPassingYards(rs.getInt(5));
summary.setPassingTd(rs.getInt(6));
summary.setInterceptions(rs.getInt(7));
summary.setRushes(rs.getInt(8));
summary.setRushYards(rs.getInt(9));
summary.setReceptions(rs.getInt(10));
summary.setReceptionYards(rs.getInt(11));
summary.setTotalTd(rs.getInt(12));
return summary;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test.football.internal;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.batch.core.test.football.domain.PlayerSummary;
import org.springframework.jdbc.core.RowMapper;
/**
* RowMapper used to map a ResultSet to a {@link org.springframework.batch.core.test.football.domain.PlayerSummary}
*
* @author Lucas Ward
* @author Mahmoud Ben Hassine
*
*/
public class PlayerSummaryRowMapper implements RowMapper<PlayerSummary> {
/* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public PlayerSummary mapRow(ResultSet rs, int rowNum) throws SQLException {
PlayerSummary summary = new PlayerSummary();
summary.setId(rs.getString(1));
summary.setYear(rs.getInt(2));
summary.setCompletes(rs.getInt(3));
summary.setAttempts(rs.getInt(4));
summary.setPassingYards(rs.getInt(5));
summary.setPassingTd(rs.getInt(6));
summary.setInterceptions(rs.getInt(7));
summary.setRushes(rs.getInt(8));
summary.setRushYards(rs.getInt(9));
summary.setReceptions(rs.getInt(10));
summary.setReceptionYards(rs.getInt(11));
summary.setTotalTd(rs.getInt(12));
return summary;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2005-2014 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
*
* https://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.core.test.ldif;
import static org.junit.Assert.assertEquals;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.net.MalformedURLException;
import org.junit.Before;
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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/simple-job-launcher-context.xml", "/applicationContext-test1.xml"})
public class LdifReaderTests {
private Resource expected;
private Resource actual;
@Autowired
private JobLauncher jobLauncher;
@Autowired
@Qualifier("job1")
private Job job1;
@Autowired
@Qualifier("job2")
private Job job2;
public LdifReaderTests() throws MalformedURLException {
expected = new ClassPathResource("/expectedOutput.ldif");
actual = new UrlResource("file:target/test-outputs/output.ldif");
}
@Before
public void checkFiles() {
Assert.isTrue(expected.exists(), "Expected does not exist.");
}
@Test
public void testValidRun() throws Exception {
JobExecution jobExecution = jobLauncher.run(job1, new JobParameters());
//Ensure job completed successfully.
Assert.isTrue(jobExecution.getExitStatus().equals(ExitStatus.COMPLETED), "Step Execution did not complete normally: " + jobExecution.getExitStatus());
//Check output.
Assert.isTrue(actual.exists(), "Actual does not exist.");
compareFiles(expected.getFile(), actual.getFile());
}
@Test
public void testResourceNotExists() throws Exception {
JobExecution jobExecution = jobLauncher.run(job2, new JobParameters());
Assert.isTrue(jobExecution.getExitStatus().getExitCode().equals("FAILED"), "The job exit status is not FAILED.");
Assert.isTrue(jobExecution.getAllFailureExceptions().get(0).getMessage().contains("Failed to initialize the reader"), "The job failed for the wrong reason.");
}
private void compareFiles(File expected, File actual) throws Exception {
BufferedReader expectedReader = new BufferedReader(new FileReader(expected));
BufferedReader actualReader = new BufferedReader(new FileReader(actual));
try {
int lineNum = 1;
for (String expectedLine = null; (expectedLine = expectedReader.readLine()) != null; lineNum++) {
String actualLine = actualReader.readLine();
assertEquals("Line number " + lineNum + " does not match.", expectedLine, actualLine);
}
String actualLine = actualReader.readLine();
assertEquals("More lines than expected. There should not be a line number " + lineNum + ".", null, actualLine);
}
finally {
expectedReader.close();
actualReader.close();
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2005-2014 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
*
* https://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.core.test.ldif;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/simple-job-launcher-context.xml", "/applicationContext-test2.xml"})
public class MappingLdifReaderTests {
private static Logger log = LoggerFactory.getLogger(MappingLdifReaderTests.class);
private Resource expected;
private Resource actual;
@Autowired
private JobLauncher launcher;
@Autowired
@Qualifier("job1")
private Job job1;
@Autowired
@Qualifier("job2")
private Job job2;
public MappingLdifReaderTests() throws MalformedURLException {
expected = new ClassPathResource("/expectedOutput.ldif");
actual = new UrlResource("file:target/test-outputs/output.ldif");
}
@Before
public void checkFiles() {
Assert.isTrue(expected.exists(), "Expected does not exist.");
}
@Test
public void testValidRun() throws Exception {
JobExecution jobExecution = launcher.run(job1, new JobParameters());
//Ensure job completed successfully.
Assert.isTrue(jobExecution.getExitStatus().equals(ExitStatus.COMPLETED), "Step Execution did not complete normally: " + jobExecution.getExitStatus());
//Check output.
Assert.isTrue(actual.exists(), "Actual does not exist.");
Assert.isTrue(compareFiles(expected.getFile(), actual.getFile()), "Files were not equal");
}
@Test
public void testResourceNotExists() throws Exception {
JobExecution jobExecution = launcher.run(job2, new JobParameters());
Assert.isTrue(jobExecution.getExitStatus().getExitCode().equals("FAILED"), "The job exit status is not FAILED.");
Assert.isTrue(jobExecution.getAllFailureExceptions().get(0).getMessage().contains("Failed to initialize the reader"), "The job failed for the wrong reason.");
}
private boolean compareFiles(File expected, File actual) throws Exception {
boolean equal = true;
FileInputStream expectedStream = new FileInputStream(expected);
FileInputStream actualStream = new FileInputStream(actual);
//Construct BufferedReader from InputStreamReader
BufferedReader expectedReader = new BufferedReader(new InputStreamReader(expectedStream));
BufferedReader actualReader = new BufferedReader(new InputStreamReader(actualStream));
String line = null;
while ((line = expectedReader.readLine()) != null) {
if(!line.equals(actualReader.readLine())) {
equal = false;
break;
}
}
if(actualReader.readLine() != null) {
equal = false;
}
expectedReader.close();
return equal;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2005-2019 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
*
* https://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.core.test.ldif;
import org.springframework.batch.item.ldif.RecordMapper;
import org.springframework.lang.Nullable;
import org.springframework.ldap.core.LdapAttributes;
/**
* This default implementation simply returns the LdapAttributes object and is only intended for test. As its not required
* to return an object of a specific type to make the MappingLdifReader implementation work, this basic setting is sufficient
* to demonstrate its function.
*
* @author Keith Barlow
*
*/
public class MyMapper implements RecordMapper<LdapAttributes> {
@Nullable
public LdapAttributes mapRecord(LdapAttributes attributes) {
return attributes;
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2017 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
*
* https://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.core.test.ldif.builder;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ldif.LdifReader;
import org.springframework.batch.item.ldif.RecordCallbackHandler;
import org.springframework.batch.item.ldif.builder.LdifReaderBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.LdapAttributes;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
* @author Glenn Renfro
*/
@RunWith(SpringRunner.class)
public class LdifReaderBuilderTests {
@Autowired
private ApplicationContext context;
private LdifReader ldifReader;
private String callbackAttributeName;
@After
public void tearDown() {
this.callbackAttributeName = null;
if (this.ldifReader != null) {
this.ldifReader.close();
}
}
@Test
public void testSkipRecord() throws Exception {
this.ldifReader = new LdifReaderBuilder().recordsToSkip(1).resource(context.getResource("classpath:/test.ldif"))
.name("foo").build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the second record did not match expected result",
"cn=Bjorn Jensen, ou=Accounting, dc=airius, dc=com", ldapAttributes.getName().toString());
}
@Test
public void testBasicRead() throws Exception {
this.ldifReader = new LdifReaderBuilder().resource(context.getResource("classpath:/test.ldif")).name("foo").build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the first record did not match expected result",
"cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com", ldapAttributes.getName().toString());
}
@Test
public void testCurrentItemCount() throws Exception {
this.ldifReader = new LdifReaderBuilder().currentItemCount(3)
.resource(context.getResource("classpath:/test.ldif")).name("foo").build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the third record did not match expected result",
"cn=Gern Jensen, ou=Product Testing, dc=airius, dc=com", ldapAttributes.getName().toString());
}
@Test
public void testMaxItemCount() throws Exception {
this.ldifReader = new LdifReaderBuilder().maxItemCount(1).resource(context.getResource("classpath:/test.ldif"))
.name("foo").build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the first record did not match expected result",
"cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com", ldapAttributes.getName().toString());
ldapAttributes = this.ldifReader.read();
assertNull("The second read should have returned null", ldapAttributes);
}
@Test
public void testSkipRecordCallback() throws Exception {
this.ldifReader = new LdifReaderBuilder().recordsToSkip(1).skippedRecordsCallback(new TestCallBackHandler())
.resource(context.getResource("classpath:/test.ldif")).name("foo").build();
firstRead();
assertEquals("The attribute name from the callback handler did not match the expected result",
"cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com", this.callbackAttributeName);
}
@Test
public void testSaveState() throws Exception {
this.ldifReader = new LdifReaderBuilder().resource(context.getResource("classpath:/test.ldif")).name("foo").build();
ExecutionContext executionContext = new ExecutionContext();
firstRead(executionContext);
this.ldifReader.update(executionContext);
assertEquals("foo.read.count did not have the expected result", 1,
executionContext.getInt("foo.read.count"));
}
@Test
public void testSaveStateDisabled() throws Exception {
this.ldifReader = new LdifReaderBuilder().saveState(false).resource(context.getResource("classpath:/test.ldif"))
.build();
ExecutionContext executionContext = new ExecutionContext();
firstRead(executionContext);
this.ldifReader.update(executionContext);
assertEquals("ExecutionContext should have been empty", 0, executionContext.size());
}
@Test
public void testStrict() {
// Test that strict when enabled will throw an exception.
try {
this.ldifReader = new LdifReaderBuilder().resource(context.getResource("classpath:/teadsfst.ldif")).name("foo").build();
this.ldifReader.open(new ExecutionContext());
fail("IllegalStateException should have been thrown, because strict was set to true");
}
catch (ItemStreamException ise) {
assertEquals("IllegalStateException message did not match the expected result.",
"Failed to initialize the reader", ise.getMessage());
}
// Test that strict when disabled will still allow the ldap resource to be opened.
this.ldifReader = new LdifReaderBuilder().strict(false)
.resource(context.getResource("classpath:/teadsfst.ldif")).name("foo").build();
this.ldifReader.open(new ExecutionContext());
}
private LdapAttributes firstRead() throws Exception {
return firstRead(new ExecutionContext());
}
private LdapAttributes firstRead(ExecutionContext executionContext) throws Exception {
this.ldifReader.open(executionContext);
return this.ldifReader.read();
}
@Configuration
public static class LdifConfiguration {
}
public class TestCallBackHandler implements RecordCallbackHandler {
@Override
public void handleRecord(LdapAttributes attributes) {
callbackAttributeName = attributes.getName().toString();
}
}
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright 2017-2019 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
*
* https://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.core.test.ldif.builder;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ldif.MappingLdifReader;
import org.springframework.batch.item.ldif.RecordCallbackHandler;
import org.springframework.batch.item.ldif.RecordMapper;
import org.springframework.batch.item.ldif.builder.MappingLdifReaderBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.ldap.core.LdapAttributes;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
* @author Glenn Renfro
*/
@RunWith(SpringRunner.class)
public class MappingLdifReaderBuilderTests {
@Autowired
private ApplicationContext context;
private MappingLdifReader<LdapAttributes> mappingLdifReader;
private String callbackAttributeName;
@After
public void tearDown() {
this.callbackAttributeName = null;
if (this.mappingLdifReader != null) {
this.mappingLdifReader.close();
}
}
@Test
public void testSkipRecord() throws Exception {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.recordsToSkip(1)
.recordMapper(new TestMapper())
.resource(context.getResource("classpath:/test.ldif"))
.name("foo")
.build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the second record did not match expected result",
"cn=Bjorn Jensen, ou=Accounting, dc=airius, dc=com", ldapAttributes.getName().toString());
}
@Test
public void testBasicRead() throws Exception {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.recordMapper(new TestMapper())
.resource(context.getResource("classpath:/test.ldif"))
.name("foo")
.build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the first record did not match expected result",
"cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com", ldapAttributes.getName().toString());
}
@Test
public void testCurrentItemCount() throws Exception {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.currentItemCount(3)
.recordMapper(new TestMapper())
.resource(context.getResource("classpath:/test.ldif"))
.name("foo")
.build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the third record did not match expected result",
"cn=Gern Jensen, ou=Product Testing, dc=airius, dc=com", ldapAttributes.getName().toString());
}
@Test
public void testMaxItemCount() throws Exception {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.maxItemCount(1)
.recordMapper(new TestMapper())
.resource(context.getResource("classpath:/test.ldif"))
.name("foo")
.build();
LdapAttributes ldapAttributes = firstRead();
assertEquals("The attribute name for the first record did not match expected result",
"cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com", ldapAttributes.getName().toString());
ldapAttributes = this.mappingLdifReader.read();
assertNull("The second read should have returned null", ldapAttributes);
}
@Test
public void testSkipRecordCallback() throws Exception {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.recordsToSkip(1)
.recordMapper(new TestMapper())
.skippedRecordsCallback(new TestCallBackHandler())
.resource(context.getResource("classpath:/test.ldif"))
.name("foo")
.build();
firstRead();
assertEquals("The attribute name from the callback handler did not match the expected result",
"cn=Barbara Jensen, ou=Product Development, dc=airius, dc=com", this.callbackAttributeName);
}
@Test
public void testSaveState() throws Exception {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.recordMapper(new TestMapper())
.resource(context.getResource("classpath:/test.ldif"))
.name("foo")
.build();
ExecutionContext executionContext = new ExecutionContext();
firstRead(executionContext);
this.mappingLdifReader.update(executionContext);
assertEquals("foo.read.count did not have the expected result", 1,
executionContext.getInt("foo.read.count"));
}
@Test
public void testSaveStateDisabled() throws Exception {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.saveState(false)
.recordMapper(new TestMapper())
.resource(context.getResource("classpath:/test.ldif"))
.build();
ExecutionContext executionContext = new ExecutionContext();
firstRead(executionContext);
this.mappingLdifReader.update(executionContext);
assertEquals("ExecutionContext should have been empty", 0, executionContext.size());
}
@Test
public void testStrict() {
// Test that strict when enabled will throw an exception.
try {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.recordMapper(new TestMapper())
.resource(context.getResource("classpath:/teadsfst.ldif"))
.name("foo")
.build();
this.mappingLdifReader.open(new ExecutionContext());
fail("IllegalStateException should have been thrown, because strict was set to true");
}
catch (ItemStreamException ise) {
assertEquals("IllegalStateException message did not match the expected result.",
"Failed to initialize the reader", ise.getMessage());
}
// Test that strict when disabled will still allow the ldap resource to be opened.
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>().strict(false).name("foo")
.recordMapper(new TestMapper()).resource(context.getResource("classpath:/teadsfst.ldif")).build();
this.mappingLdifReader.open(new ExecutionContext());
}
@Test
public void testNullRecordMapper() {
try {
this.mappingLdifReader = new MappingLdifReaderBuilder<LdapAttributes>()
.resource(context.getResource("classpath:/teadsfst.ldif"))
.build();
fail("IllegalArgumentException should have been thrown");
}
catch (IllegalArgumentException ise) {
assertEquals("IllegalArgumentException message did not match the expected result.",
"RecordMapper is required.", ise.getMessage());
}
}
private LdapAttributes firstRead() throws Exception {
return firstRead(new ExecutionContext());
}
private LdapAttributes firstRead(ExecutionContext executionContext) throws Exception {
this.mappingLdifReader.open(executionContext);
return this.mappingLdifReader.read();
}
@Configuration
public static class LdifConfiguration {
}
public class TestCallBackHandler implements RecordCallbackHandler {
@Override
public void handleRecord(LdapAttributes attributes) {
callbackAttributeName = attributes.getName().toString();
}
}
public class TestMapper implements RecordMapper<LdapAttributes> {
@Nullable
@Override
public LdapAttributes mapRecord(LdapAttributes attributes) {
return attributes;
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.batch.test.namespace.config;
package org.springframework.batch.core.test.namespace.config;
import java.util.Random;

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2006-2009 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
*
* https://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.core.test.repository;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.dao.MapExecutionContextDao;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
/**
* @author Dave Syer
*
*/
public class ConcurrentMapExecutionContextDaoTests {
private MapExecutionContextDao dao = new MapExecutionContextDao();
private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
@Test
public void testSaveUpdate() throws Exception {
StepExecution stepExecution = new StepExecution("step", new JobExecution(11L));
stepExecution.setId(123L);
stepExecution.getExecutionContext().put("foo", "bar");
dao.saveExecutionContext(stepExecution);
ExecutionContext executionContext = dao.getExecutionContext(stepExecution);
assertEquals("bar", executionContext.get("foo"));
}
@Test
public void testTransactionalSaveUpdate() throws Exception {
final StepExecution stepExecution = new StepExecution("step", new JobExecution(11L));
stepExecution.setId(123L);
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
stepExecution.getExecutionContext().put("foo", "bar");
dao.saveExecutionContext(stepExecution);
return null;
}
});
ExecutionContext executionContext = dao.getExecutionContext(stepExecution);
assertEquals("bar", executionContext.get("foo"));
}
@Test
public void testConcurrentTransactionalSaveUpdate() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletionService<StepExecution> completionService = new ExecutorCompletionService<>(executor);
final int outerMax = 10;
final int innerMax = 100;
for (int i = 0; i < outerMax; i++) {
final StepExecution stepExecution1 = new StepExecution("step", new JobExecution(11L));
stepExecution1.setId(123L + i);
final StepExecution stepExecution2 = new StepExecution("step", new JobExecution(11L));
stepExecution2.setId(1234L + i);
completionService.submit(new Callable<StepExecution>() {
@Override
public StepExecution call() throws Exception {
for (int i = 0; i < innerMax; i++) {
String value = "bar" + i;
saveAndAssert(stepExecution1, value);
}
return stepExecution1;
}
});
completionService.submit(new Callable<StepExecution>() {
@Override
public StepExecution call() throws Exception {
for (int i = 0; i < innerMax; i++) {
String value = "spam" + i;
saveAndAssert(stepExecution2, value);
}
return stepExecution2;
}
});
completionService.take().get();
completionService.take().get();
}
executor.shutdown();
}
private void saveAndAssert(final StepExecution stepExecution, final String value) {
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
stepExecution.getExecutionContext().put("foo", value);
dao.saveExecutionContext(stepExecution);
return null;
}
});
ExecutionContext executionContext = dao.getExecutionContext(stepExecution);
Assert.state(executionContext != null, "Lost insert: null executionContext at value=" + value);
String foo = executionContext.getString("foo");
Assert.state(value.equals(foo), "Lost update: wrong value=" + value + " (found " + foo + ") for id="
+ stepExecution.getId());
}
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test.repository;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.test.AbstractIntegrationTests;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/simple-job-launcher-context.xml"})
public class JdbcJobRepositoryTests extends AbstractIntegrationTests {
private JobSupport job;
private Set<Long> jobExecutionIds = new HashSet<>();
private Set<Long> jobIds = new HashSet<>();
private List<Serializable> list = new ArrayList<>();
private JdbcTemplate jdbcTemplate;
@Autowired
private JobRepository repository;
/** Logger */
private final Log logger = LogFactory.getLog(getClass());
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Before
public void onSetUpInTransaction() throws Exception {
super.setUp();
job = new JobSupport("test-job");
job.setRestartable(true);
}
@Test
public void testFindOrCreateJob() throws Exception {
job.setName("foo");
int before = 0;
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
int after = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE", Integer.class);
assertEquals(before + 1, after);
assertNotNull(execution.getId());
}
@Test
public void testFindOrCreateJobWithExecutionContext() throws Exception {
job.setName("foo");
int before = 0;
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
execution.getExecutionContext().put("foo", "bar");
repository.updateExecutionContext(execution);
int after = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM BATCH_JOB_EXECUTION_CONTEXT", Integer.class);
assertEquals(before + 1, after);
assertNotNull(execution.getId());
JobExecution last = repository.getLastJobExecution(job.getName(), new JobParameters());
assertEquals(execution, last);
assertEquals(execution.getExecutionContext(), last.getExecutionContext());
}
@Test
public void testFindOrCreateJobConcurrently() throws Exception {
job.setName("bar");
int before = 0;
assertEquals(0, before);
long t0 = System.currentTimeMillis();
try {
doConcurrentStart();
fail("Expected JobExecutionAlreadyRunningException");
}
catch (JobExecutionAlreadyRunningException e) {
// expected
}
long t1 = System.currentTimeMillis();
JobExecution execution = (JobExecution) list.get(0);
assertNotNull(execution);
int after = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE", Integer.class);
assertNotNull(execution.getId());
assertEquals(before + 1, after);
logger.info("Duration: " + (t1 - t0)
+ " - the second transaction did not block if this number is less than about 1000.");
}
@Test
public void testFindOrCreateJobConcurrentlyWhenJobAlreadyExists() throws Exception {
job = new JobSupport("test-job");
job.setRestartable(true);
job.setName("spam");
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
cacheJobIds(execution);
execution.setEndTime(new Timestamp(System.currentTimeMillis()));
repository.update(execution);
execution.setStatus(BatchStatus.FAILED);
int before = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE", Integer.class);
assertEquals(1, before);
long t0 = System.currentTimeMillis();
try {
doConcurrentStart();
fail("Expected JobExecutionAlreadyRunningException");
}
catch (JobExecutionAlreadyRunningException e) {
// expected
}
long t1 = System.currentTimeMillis();
int after = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE", Integer.class);
assertNotNull(execution.getId());
assertEquals(before, after);
logger.info("Duration: " + (t1 - t0)
+ " - the second transaction did not block if this number is less than about 1000.");
}
private void cacheJobIds(JobExecution execution) {
if (execution == null) {
return;
}
jobExecutionIds.add(execution.getId());
jobIds.add(execution.getJobId());
}
private JobExecution doConcurrentStart() throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
try {
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
//simulate running execution
execution.setStartTime(new Date());
repository.update(execution);
cacheJobIds(execution);
list.add(execution);
Thread.sleep(1000);
}
catch (Exception e) {
list.add(e);
}
}
}).start();
Thread.sleep(400);
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
cacheJobIds(execution);
int count = 0;
while (list.size() == 0 && count++ < 100) {
Thread.sleep(200);
}
assertEquals("Timed out waiting for JobExecution to be created", 1, list.size());
assertTrue("JobExecution not created in thread: " + list.get(0), list.get(0) instanceof JobExecution);
return (JobExecution) list.get(0);
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test.repository;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.JobParametersValidator;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.job.DefaultJobParametersValidator;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* Batch domain object representing a job. Job is an explicit abstraction
* representing the configuration of a job specified by a developer. It should
* be noted that restart policy is applied to the job as a whole and not to a
* step.
*
* @author Lucas Ward
* @author Dave Syer
*/
public class JobSupport implements BeanNameAware, Job {
private List<Step> steps = new ArrayList<>();
private String name;
private boolean restartable = false;
private int startLimit = Integer.MAX_VALUE;
private JobParametersValidator jobParametersValidator = new DefaultJobParametersValidator();
/**
* Default constructor.
*/
public JobSupport() {
super();
}
/**
* Convenience constructor to immediately add name (which is mandatory but
* not final).
*
* @param name the name
*/
public JobSupport(String name) {
super();
this.name = name;
}
/**
* Set the name property if it is not already set. Because of the order of
* the callbacks in a Spring container the name property will be set first
* if it is present. Care is needed with bean definition inheritance - if a
* parent bean has a name, then its children need an explicit name as well,
* otherwise they will not be unique.
*
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
*/
@Override
public void setBeanName(String name) {
if (this.name == null) {
this.name = name;
}
}
/**
* Set the name property. Always overrides the default value if this object
* is a Spring bean.
*
* @see #setBeanName(java.lang.String)
* @param name the name
*/
public void setName(String name) {
this.name = name;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.IJob#getName()
*/
@Override
public String getName() {
return name;
}
/**
* @param jobParametersValidator the jobParametersValidator to set
*/
public void setJobParametersValidator(JobParametersValidator jobParametersValidator) {
this.jobParametersValidator = jobParametersValidator;
}
public void setSteps(List<Step> steps) {
this.steps.clear();
this.steps.addAll(steps);
}
public void addStep(Step step) {
this.steps.add(step);
}
public List<Step> getSteps() {
return steps;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.IJob#getStartLimit()
*/
public int getStartLimit() {
return startLimit;
}
public void setStartLimit(int startLimit) {
this.startLimit = startLimit;
}
public void setRestartable(boolean restartable) {
this.restartable = restartable;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.IJob#isRestartable()
*/
@Override
public boolean isRestartable() {
return restartable;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.Job#getJobParametersIncrementer()
*/
@Nullable
@Override
public JobParametersIncrementer getJobParametersIncrementer() {
return null;
}
@Override
public JobParametersValidator getJobParametersValidator() {
return jobParametersValidator;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.Job#run(org.springframework.batch.core.domain.JobExecution)
*/
@Override
public void execute(JobExecution execution) throws UnexpectedJobExecutionException {
throw new UnsupportedOperationException("JobSupport does not provide an implementation of run(). Use a smarter subclass.");
}
@Override
public String toString() {
return ClassUtils.getShortName(getClass()) + ": [name=" + name + "]";
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2020 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
*
* https://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.core.test.repository;
import java.util.Date;
import java.util.List;
import javax.sql.DataSource;
import com.mysql.cj.jdbc.MysqlDataSource;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.testcontainers.containers.MySQLContainer;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.JobRegistry;
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.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.support.SimpleJobOperator;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Mahmoud Ben Hassine
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@Ignore("Temporarily ignored until integration tests profile is created")
public class MySQLJdbcJobRepositoryTests {
@ClassRule
public static MySQLContainer mysql = new MySQLContainer<>();
@Autowired
private DataSource dataSource;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobOperator jobOperator;
@Autowired
private Job job;
@Before
public void setUp() {
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.addScript(new ClassPathResource("/org/springframework/batch/core/schema-mysql.sql"));
databasePopulator.execute(this.dataSource);
}
/*
* This test is for issue https://github.com/spring-projects/spring-batch/issues/2202:
* A round trip from a `java.util.Date` JobParameter to the database and back
* again should preserve fractional seconds precision, otherwise a different
* job instance is created while the existing one should be used.
*
* This test ensures that round trip to the database with a `java.util.Date`
* parameter ends up with a single job instance (with two job executions)
* being created and not two distinct job instances (with a job execution for
* each one).
*
* Note the issue does not happen if the parameter is of type Long (when using
* addLong("date", date.getTime()) for instance).
*/
@Test
public void testDateMillisecondPrecision() throws Exception {
// given
Date date = new Date();
JobParameters jobParameters = new JobParametersBuilder()
.addDate("date", date)
.toJobParameters();
// when
JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters);
this.jobOperator.restart(jobExecution.getId()); // should load the date parameter with fractional seconds precision here
// then
List<Long> jobInstances = this.jobOperator.getJobInstances("job", 0, 100);
Assert.assertEquals(1, jobInstances.size());
List<Long> jobExecutions = this.jobOperator.getExecutions(jobInstances.get(0));
Assert.assertEquals(2, jobExecutions.size());
}
@Configuration
@EnableBatchProcessing
static class TestConfiguration {
@Bean
public DataSource dataSource() {
MysqlDataSource datasource = new MysqlDataSource();
datasource.setURL(mysql.getJdbcUrl());
datasource.setUser(mysql.getUsername());
datasource.setPassword(mysql.getPassword());
return datasource;
}
@Bean
public Job job(JobBuilderFactory jobs, StepBuilderFactory steps) {
return jobs.get("job")
.start(steps.get("step")
.tasklet((contribution, chunkContext) -> {
throw new Exception("expected failure");
})
.build())
.build();
}
@Bean
public JobOperator jobOperator(
JobLauncher jobLauncher,
JobRegistry jobRegistry,
JobExplorer jobExplorer,
JobRepository jobRepository
) {
SimpleJobOperator jobOperator = new SimpleJobOperator();
jobOperator.setJobExplorer(jobExplorer);
jobOperator.setJobLauncher(jobLauncher);
jobOperator.setJobRegistry(jobRegistry);
jobOperator.setJobRepository(jobRepository);
return jobOperator;
}
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
return jobRegistryBeanPostProcessor;
}
}
}

View File

@@ -0,0 +1,288 @@
/*
* Copyright 2010-2019 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
*
* https://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.core.test.step;
import static org.junit.Assert.assertEquals;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
/**
* Tests for {@link FaultTolerantStepFactoryBean}.
*/
@ContextConfiguration(locations = "/simple-job-launcher-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class FaultTolerantStepFactoryBeanIntegrationTests {
private static final int MAX_COUNT = 1000;
private final Log logger = LogFactory.getLog(getClass());
private FaultTolerantStepFactoryBean<String, String> factory;
private SkipProcessorStub processor;
private SkipWriterStub writer;
private JobExecution jobExecution;
private StepExecution stepExecution;
@Autowired
private DataSource dataSource;
@Autowired
private JobRepository repository;
@Autowired
private PlatformTransactionManager transactionManager;
@Before
public void setUp() throws Exception {
writer = new SkipWriterStub(dataSource);
processor = new SkipProcessorStub(dataSource);
factory = new FaultTolerantStepFactoryBean<>();
factory.setBeanName("stepName");
factory.setTransactionManager(transactionManager);
factory.setJobRepository(repository);
factory.setCommitInterval(3);
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3);
taskExecutor.setMaxPoolSize(6);
taskExecutor.setQueueCapacity(0);
taskExecutor.afterPropertiesSet();
factory.setTaskExecutor(taskExecutor);
JdbcTestUtils.deleteFromTables(new JdbcTemplate(dataSource), "ERROR_LOG");
}
@Test
public void testUpdatesNoRollback() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
writer.write(Arrays.asList("foo", "bar"));
processor.process("spam");
assertEquals(3, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG"));
writer.clear();
processor.clear();
assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG"));
}
@Test
public void testMultithreadedSunnyDay() throws Throwable {
jobExecution = repository.createJobExecution("vanillaJob", new JobParameters());
for (int i = 0; i < MAX_COUNT; i++) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
SkipReaderStub reader = new SkipReaderStub();
reader.clear();
reader.setItems("1", "2", "3", "4", "5");
factory.setItemReader(reader);
writer.clear();
factory.setItemWriter(writer);
processor.clear();
factory.setItemProcessor(processor);
assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG"));
try {
Step step = factory.getObject();
stepExecution = jobExecution.createStepExecution(factory.getName());
repository.add(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
List<String> committed = new ArrayList<>(writer.getCommitted());
Collections.sort(committed);
assertEquals("[1, 2, 3, 4, 5]", committed.toString());
List<String> processed = new ArrayList<>(processor.getCommitted());
Collections.sort(processed);
assertEquals("[1, 2, 3, 4, 5]", processed.toString());
assertEquals(0, stepExecution.getSkipCount());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
private static class SkipReaderStub implements ItemReader<String> {
private String[] items;
private int counter = -1;
public SkipReaderStub() throws Exception {
super();
}
public void setItems(String... items) {
Assert.isTrue(counter < 0, "Items cannot be set once reading has started");
this.items = items;
}
public void clear() {
counter = -1;
}
@Nullable
@Override
public synchronized String read() throws Exception, UnexpectedInputException, ParseException {
counter++;
if (counter >= items.length) {
return null;
}
String item = items[counter];
return item;
}
}
private static class SkipWriterStub implements ItemWriter<String> {
private List<String> written = new ArrayList<>();
private Collection<String> failures = Collections.emptySet();
private JdbcTemplate jdbcTemplate;
public SkipWriterStub(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<String> getCommitted() {
return jdbcTemplate.query("SELECT MESSAGE from ERROR_LOG where STEP_NAME='written'",
new RowMapper<String>() {
@Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString(1);
}
});
}
public void clear() {
written.clear();
jdbcTemplate.update("DELETE FROM ERROR_LOG where STEP_NAME='written'");
}
@Override
public void write(List<? extends String> items) throws Exception {
for (String item : items) {
written.add(item);
jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "written");
checkFailure(item);
}
}
private void checkFailure(String item) {
if (failures.contains(item)) {
throw new RuntimeException("Planned failure");
}
}
}
private static class SkipProcessorStub implements ItemProcessor<String, String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> processed = new ArrayList<>();
private JdbcTemplate jdbcTemplate;
/**
* @param dataSource
*/
public SkipProcessorStub(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<String> getCommitted() {
return jdbcTemplate.query("SELECT MESSAGE from ERROR_LOG where STEP_NAME='processed'",
new RowMapper<String>() {
@Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString(1);
}
});
}
public void clear() {
processed.clear();
jdbcTemplate.update("DELETE FROM ERROR_LOG where STEP_NAME='processed'");
}
@Nullable
@Override
public String process(String item) throws Exception {
processed.add(item);
logger.debug("Processed item: "+item);
jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "processed");
return item;
}
}
}

View File

@@ -0,0 +1,320 @@
/*
* Copyright 2010-2019 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
*
* https://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.core.test.step;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link FaultTolerantStepFactoryBean}.
*/
@ContextConfiguration(locations = "/simple-job-launcher-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class FaultTolerantStepFactoryBeanRollbackIntegrationTests {
private static final int MAX_COUNT = 1000;
private final Log logger = LogFactory.getLog(getClass());
private FaultTolerantStepFactoryBean<String, String> factory;
private SkipProcessorStub processor;
private SkipWriterStub writer;
private JobExecution jobExecution;
private StepExecution stepExecution;
@Autowired
private DataSource dataSource;
@Autowired
private JobRepository repository;
@Autowired
private PlatformTransactionManager transactionManager;
@Before
public void setUp() throws Exception {
writer = new SkipWriterStub(dataSource);
processor = new SkipProcessorStub(dataSource);
factory = new FaultTolerantStepFactoryBean<>();
factory.setBeanName("stepName");
factory.setTransactionManager(transactionManager);
factory.setJobRepository(repository);
factory.setCommitInterval(3);
factory.setSkipLimit(10);
JdbcTestUtils.deleteFromTables(new JdbcTemplate(dataSource), "ERROR_LOG");
}
@Test
public void testUpdatesNoRollback() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
writer.write(Arrays.asList("foo", "bar"));
processor.process("spam");
assertEquals(3, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG"));
writer.clear();
processor.clear();
assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG"));
}
@Test
public void testMultithreadedSkipInWriter() throws Throwable {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3);
taskExecutor.setMaxPoolSize(6);
taskExecutor.setQueueCapacity(0);
taskExecutor.afterPropertiesSet();
factory.setTaskExecutor(taskExecutor);
@SuppressWarnings("unchecked")
Map<Class<? extends Throwable>, Boolean> skippable = getExceptionMap(Exception.class);
factory.setSkippableExceptionClasses(skippable);
jobExecution = repository.createJobExecution("skipJob", new JobParameters());
for (int i = 0; i < MAX_COUNT; i++) {
if (i % 100 == 0) {
logger.info("Starting step: " + i);
}
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG"));
try {
SkipReaderStub reader = new SkipReaderStub();
reader.clear();
reader.setItems("1", "2", "3", "4", "5");
factory.setItemReader(reader);
writer.clear();
factory.setItemWriter(writer);
processor.clear();
factory.setItemProcessor(processor);
writer.setFailures("1", "2", "3", "4", "5");
Step step = factory.getObject();
stepExecution = jobExecution.createStepExecution(factory.getName());
repository.add(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals("[]", writer.getCommitted().toString());
assertEquals("[]", processor.getCommitted().toString());
List<String> processed = new ArrayList<>(processor.getProcessed());
Collections.sort(processed);
assertEquals("[1, 1, 2, 2, 3, 3, 4, 4, 5, 5]", processed.toString());
assertEquals(5, stepExecution.getSkipCount());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
@SuppressWarnings("unchecked")
private Map<Class<? extends Throwable>, Boolean> getExceptionMap(Class<? extends Throwable>... args) {
Map<Class<? extends Throwable>, Boolean> map = new HashMap<>();
for (Class<? extends Throwable> arg : args) {
map.put(arg, true);
}
return map;
}
private static class SkipReaderStub implements ItemReader<String> {
private String[] items;
private int counter = -1;
public SkipReaderStub() throws Exception {
super();
}
public void setItems(String... items) {
Assert.isTrue(counter < 0, "Items cannot be set once reading has started");
this.items = items;
}
public void clear() {
counter = -1;
}
@Nullable
@Override
public synchronized String read() throws Exception, UnexpectedInputException, ParseException {
counter++;
if (counter >= items.length) {
return null;
}
String item = items[counter];
return item;
}
}
private static class SkipWriterStub implements ItemWriter<String> {
private List<String> written = new CopyOnWriteArrayList<>();
private Collection<String> failures = Collections.emptySet();
private JdbcTemplate jdbcTemplate;
public SkipWriterStub(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setFailures(String... failures) {
this.failures = Arrays.asList(failures);
}
public List<String> getCommitted() {
return jdbcTemplate.query("SELECT MESSAGE from ERROR_LOG where STEP_NAME='written'",
new RowMapper<String>() {
@Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString(1);
}
});
}
public void clear() {
written.clear();
jdbcTemplate.update("DELETE FROM ERROR_LOG where STEP_NAME='written'");
}
@Override
public void write(List<? extends String> items) throws Exception {
for (String item : items) {
written.add(item);
jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "written");
checkFailure(item);
}
}
private void checkFailure(String item) {
if (failures.contains(item)) {
throw new RuntimeException("Planned failure");
}
}
}
private static class SkipProcessorStub implements ItemProcessor<String, String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> processed = new CopyOnWriteArrayList<>();
private JdbcTemplate jdbcTemplate;
/**
* @param dataSource
*/
public SkipProcessorStub(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* @return the processed
*/
public List<String> getProcessed() {
return processed;
}
public List<String> getCommitted() {
return jdbcTemplate.query("SELECT MESSAGE from ERROR_LOG where STEP_NAME='processed'",
new RowMapper<String>() {
@Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString(1);
}
});
}
public void clear() {
processed.clear();
jdbcTemplate.update("DELETE FROM ERROR_LOG where STEP_NAME='processed'");
}
@Nullable
@Override
public String process(String item) throws Exception {
processed.add(item);
logger.debug("Processed item: " + item);
jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "processed");
return item;
}
}
}

View File

@@ -0,0 +1,289 @@
/*
* Copyright 2010-2019 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
*
* https://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.core.test.step;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.FaultTolerantStepBuilder;
import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import static org.junit.Assert.assertEquals;
/**
* Tests for fault tolerant {@link org.springframework.batch.core.step.item.ChunkOrientedTasklet}.
*/
@ContextConfiguration(locations = "/simple-job-launcher-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class FaultTolerantStepIntegrationTests {
private static final int TOTAL_ITEMS = 30;
private static final int CHUNK_SIZE = TOTAL_ITEMS;
@Autowired
private JobRepository jobRepository;
@Autowired
private PlatformTransactionManager transactionManager;
private SkipPolicy skipPolicy;
private FaultTolerantStepBuilder<Integer, Integer> stepBuilder;
@Before
public void setUp() {
ItemReader<Integer> itemReader = new ListItemReader<>(createItems());
ItemProcessor<Integer, Integer> itemProcessor = item -> item > 20 ? null : item;
ItemWriter<Integer> itemWriter = chunk -> {
if (chunk.contains(1)) {
throw new IllegalArgumentException();
}
};
skipPolicy = new SkipIllegalArgumentExceptionSkipPolicy();
stepBuilder = new StepBuilderFactory(jobRepository, transactionManager).get("step")
.<Integer, Integer>chunk(CHUNK_SIZE)
.reader(itemReader)
.processor(itemProcessor)
.writer(itemWriter)
.faultTolerant();
}
@Test
public void testFilterCountWithTransactionalProcessorWhenSkipInWrite() throws Exception {
// Given
Step step = stepBuilder
.skipPolicy(skipPolicy)
.build();
// When
StepExecution stepExecution = execute(step);
// Then
assertEquals(TOTAL_ITEMS, stepExecution.getReadCount());
assertEquals(10, stepExecution.getFilterCount());
assertEquals(19, stepExecution.getWriteCount());
assertEquals(1, stepExecution.getWriteSkipCount());
}
@Test
public void testFilterCountWithNonTransactionalProcessorWhenSkipInWrite() throws Exception {
// Given
Step step = stepBuilder
.skipPolicy(skipPolicy)
.processorNonTransactional()
.build();
// When
StepExecution stepExecution = execute(step);
// Then
assertEquals(TOTAL_ITEMS, stepExecution.getReadCount());
assertEquals(10, stepExecution.getFilterCount());
assertEquals(19, stepExecution.getWriteCount());
assertEquals(1, stepExecution.getWriteSkipCount());
}
@Test
public void testFilterCountOnRetryWithTransactionalProcessorWhenSkipInWrite() throws Exception {
// Given
Step step = stepBuilder
.retry(IllegalArgumentException.class)
.retryLimit(2)
.skipPolicy(skipPolicy)
.build();
// When
StepExecution stepExecution = execute(step);
// Then
assertEquals(TOTAL_ITEMS, stepExecution.getReadCount());
// filter count is expected to be counted on each retry attempt
assertEquals(20, stepExecution.getFilterCount());
assertEquals(19, stepExecution.getWriteCount());
assertEquals(1, stepExecution.getWriteSkipCount());
}
@Test
public void testFilterCountOnRetryWithNonTransactionalProcessorWhenSkipInWrite() throws Exception {
// Given
Step step = stepBuilder
.retry(IllegalArgumentException.class)
.retryLimit(2)
.skipPolicy(skipPolicy)
.processorNonTransactional()
.build();
// When
StepExecution stepExecution = execute(step);
// Then
assertEquals(TOTAL_ITEMS, stepExecution.getReadCount());
// filter count is expected to be counted on each retry attempt
assertEquals(20, stepExecution.getFilterCount());
assertEquals(19, stepExecution.getWriteCount());
assertEquals(1, stepExecution.getWriteSkipCount());
}
@Test(timeout = 3000)
public void testExceptionInProcessDuringChunkScan() throws Exception {
// Given
ListItemReader<Integer> itemReader = new ListItemReader<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
ItemProcessor<Integer, Integer> itemProcessor = new ItemProcessor<Integer, Integer>() {
private int cpt;
@Nullable
@Override
public Integer process(Integer item) throws Exception {
cpt++;
if (cpt == 7) { // item 2 succeeds the first time but fails during the scan
throw new Exception("Error during process");
}
return item;
}
};
ItemWriter<Integer> itemWriter = new ItemWriter<Integer>() {
private int cpt;
@Override
public void write(List<? extends Integer> items) throws Exception {
cpt++;
if (cpt == 1) {
throw new Exception("Error during write");
}
}
};
Step step = new StepBuilderFactory(jobRepository, transactionManager).get("step")
.<Integer, Integer>chunk(5)
.reader(itemReader)
.processor(itemProcessor)
.writer(itemWriter)
.faultTolerant()
.skip(Exception.class)
.skipLimit(3)
.build();
// When
StepExecution stepExecution = execute(step);
// Then
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals(ExitStatus.COMPLETED, stepExecution.getExitStatus());
assertEquals(7, stepExecution.getReadCount());
assertEquals(6, stepExecution.getWriteCount());
assertEquals(1, stepExecution.getProcessSkipCount());
}
@Test(timeout = 3000)
public void testExceptionInProcessAndWriteDuringChunkScan() throws Exception {
// Given
ListItemReader<Integer> itemReader = new ListItemReader<>(Arrays.asList(1, 2, 3));
ItemProcessor<Integer, Integer> itemProcessor = new ItemProcessor<Integer, Integer>() {
@Override
public Integer process(Integer item) throws Exception {
if (item.equals(2)) {
throw new Exception("Error during process item " + item);
}
return item;
}
};
ItemWriter<Integer> itemWriter = new ItemWriter<Integer>() {
@Override
public void write(List<? extends Integer> items) throws Exception {
if (items.contains(3)) {
throw new Exception("Error during write");
}
}
};
Step step = new StepBuilderFactory(jobRepository, transactionManager).get("step")
.<Integer, Integer>chunk(5)
.reader(itemReader)
.processor(itemProcessor)
.writer(itemWriter)
.faultTolerant()
.skipPolicy(new AlwaysSkipItemSkipPolicy())
.build();
// When
StepExecution stepExecution = execute(step);
// Then
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals(ExitStatus.COMPLETED, stepExecution.getExitStatus());
assertEquals(3, stepExecution.getReadCount());
assertEquals(1, stepExecution.getWriteCount());
assertEquals(1, stepExecution.getWriteSkipCount());
assertEquals(1, stepExecution.getProcessSkipCount());
assertEquals(3, stepExecution.getRollbackCount());
assertEquals(2, stepExecution.getCommitCount());
}
private List<Integer> createItems() {
List<Integer> items = new ArrayList<>(TOTAL_ITEMS);
for (int i = 1; i <= TOTAL_ITEMS; i++) {
items.add(i);
}
return items;
}
private StepExecution execute(Step step) throws Exception {
JobExecution jobExecution = jobRepository.createJobExecution(
"job" + Math.random(), new JobParameters());
StepExecution stepExecution = jobExecution.createStepExecution("step");
jobRepository.add(stepExecution);
step.execute(stepExecution);
return stepExecution;
}
private class SkipIllegalArgumentExceptionSkipPolicy implements SkipPolicy {
@Override
public boolean shouldSkip(Throwable throwable, int skipCount)
throws SkipLimitExceededException {
return throwable instanceof IllegalArgumentException;
}
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright 2010-2019 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
*
* https://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.core.test.step;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link FaultTolerantStepFactoryBean}.
*/
public class MapRepositoryFaultTolerantStepFactoryBeanRollbackTests {
private static final int MAX_COUNT = 1000;
private final Log logger = LogFactory.getLog(getClass());
private FaultTolerantStepFactoryBean<String, String> factory;
private SkipReaderStub reader;
private SkipProcessorStub processor;
private SkipWriterStub writer;
private JobExecution jobExecution;
private StepExecution stepExecution;
private JobRepository repository;
private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
reader = new SkipReaderStub();
writer = new SkipWriterStub();
processor = new SkipProcessorStub();
factory = new FaultTolerantStepFactoryBean<>();
factory.setTransactionManager(transactionManager);
factory.setBeanName("stepName");
factory.setCommitInterval(3);
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3);
taskExecutor.setMaxPoolSize(6);
taskExecutor.setQueueCapacity(0);
taskExecutor.afterPropertiesSet();
factory.setTaskExecutor(taskExecutor);
factory.setSkipLimit(10);
factory.setSkippableExceptionClasses(getExceptionMap(Exception.class));
}
@Test
public void testUpdatesNoRollback() throws Exception {
writer.write(Arrays.asList("foo", "bar"));
processor.process("spam");
assertEquals(2, writer.getWritten().size());
assertEquals(1, processor.getProcessed().size());
writer.clear();
processor.clear();
assertEquals(0, processor.getProcessed().size());
}
@Test
public void testMultithreadedSkipInWrite() throws Throwable {
for (int i = 0; i < MAX_COUNT; i++) {
if (i%100==0) {
logger.info("Starting step: "+i);
repository = new MapJobRepositoryFactoryBean(transactionManager).getObject();
factory.setJobRepository(repository);
jobExecution = repository.createJobExecution("vanillaJob", new JobParameters());
}
reader.clear();
reader.setItems("1", "2", "3", "4", "5");
factory.setItemReader(reader);
writer.clear();
factory.setItemWriter(writer);
processor.clear();
factory.setItemProcessor(processor);
writer.setFailures("1", "2", "3", "4", "5");
try {
Step step = factory.getObject();
stepExecution = jobExecution.createStepExecution(factory.getName());
repository.add(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals(5, stepExecution.getSkipCount());
List<String> processed = new ArrayList<>(processor.getProcessed());
Collections.sort(processed);
assertEquals("[1, 1, 2, 2, 3, 3, 4, 4, 5, 5]", processed.toString());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
private static class SkipReaderStub implements ItemReader<String> {
private String[] items;
private int counter = -1;
public SkipReaderStub() throws Exception {
super();
}
public void setItems(String... items) {
Assert.isTrue(counter < 0, "Items cannot be set once reading has started");
this.items = items;
}
public void clear() {
counter = -1;
}
@Nullable
@Override
public synchronized String read() throws Exception {
counter++;
if (counter >= items.length) {
return null;
}
return items[counter];
}
}
private static class SkipWriterStub implements ItemWriter<String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> written = new CopyOnWriteArrayList<>();
private Collection<String> failures = Collections.emptySet();
public void setFailures(String... failures) {
this.failures = Arrays.asList(failures);
}
public List<String> getWritten() {
return written;
}
public void clear() {
written.clear();
}
@Override
public void write(List<? extends String> items) throws Exception {
for (String item : items) {
logger.trace("Writing: "+item);
written.add(item);
checkFailure(item);
}
}
private void checkFailure(String item) {
if (failures.contains(item)) {
throw new RuntimeException("Planned failure");
}
}
}
private static class SkipProcessorStub implements ItemProcessor<String, String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> processed = new CopyOnWriteArrayList<>();
public List<String> getProcessed() {
return processed;
}
public void clear() {
processed.clear();
}
@Nullable
@Override
public String process(String item) throws Exception {
processed.add(item);
logger.debug("Processed item: "+item);
return item;
}
}
@SuppressWarnings("unchecked")
private Map<Class<? extends Throwable>, Boolean> getExceptionMap(Class<? extends Throwable>... args) {
Map<Class<? extends Throwable>, Boolean> map = new HashMap<>();
for (Class<? extends Throwable> arg : args) {
map.put(arg, true);
}
return map;
}
}

View File

@@ -0,0 +1,240 @@
/*
* Copyright 2010-2019 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
*
* https://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.core.test.step;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
/**
* Tests for {@link FaultTolerantStepFactoryBean}.
*/
public class MapRepositoryFaultTolerantStepFactoryBeanTests {
private static final int MAX_COUNT = 1000;
private final Log logger = LogFactory.getLog(getClass());
private FaultTolerantStepFactoryBean<String, String> factory;
private SkipReaderStub reader;
private SkipProcessorStub processor;
private SkipWriterStub writer;
private JobExecution jobExecution;
private StepExecution stepExecution;
private JobRepository repository;
private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
@Before
public void setUp() throws Exception {
reader = new SkipReaderStub();
writer = new SkipWriterStub();
processor = new SkipProcessorStub();
factory = new FaultTolerantStepFactoryBean<>();
factory.setBeanName("stepName");
factory.setTransactionManager(transactionManager);
factory.setCommitInterval(3);
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3);
taskExecutor.setMaxPoolSize(6);
taskExecutor.setQueueCapacity(0);
taskExecutor.afterPropertiesSet();
factory.setTaskExecutor(taskExecutor);
}
@Test
public void testUpdatesNoRollback() throws Exception {
writer.write(Arrays.asList("foo", "bar"));
processor.process("spam");
assertEquals(2, writer.getWritten().size());
assertEquals(1, processor.getProcessed().size());
writer.clear();
processor.clear();
assertEquals(0, processor.getProcessed().size());
}
@Test
public void testMultithreadedSunnyDay() throws Throwable {
for (int i = 0; i < MAX_COUNT; i++) {
if (i%100==0) {
logger.info("Starting step: "+i);
repository = new MapJobRepositoryFactoryBean(transactionManager).getObject();
factory.setJobRepository(repository);
jobExecution = repository.createJobExecution("vanillaJob", new JobParameters());
}
reader.clear();
reader.setItems("1", "2", "3", "4", "5");
factory.setItemReader(reader);
writer.clear();
factory.setItemWriter(writer);
processor.clear();
factory.setItemProcessor(processor);
try {
Step step = factory.getObject();
stepExecution = jobExecution.createStepExecution(factory.getName());
repository.add(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
List<String> committed = new ArrayList<>(writer.getWritten());
Collections.sort(committed);
assertEquals("[1, 2, 3, 4, 5]", committed.toString());
List<String> processed = new ArrayList<>(processor.getProcessed());
Collections.sort(processed);
assertEquals("[1, 2, 3, 4, 5]", processed.toString());
assertEquals(0, stepExecution.getSkipCount());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
private static class SkipReaderStub implements ItemReader<String> {
private String[] items;
private int counter = -1;
public SkipReaderStub() throws Exception {
super();
}
public void setItems(String... items) {
Assert.isTrue(counter < 0, "Items cannot be set once reading has started");
this.items = items;
}
public void clear() {
counter = -1;
}
@Nullable
@Override
public synchronized String read() throws Exception, UnexpectedInputException, ParseException {
counter++;
if (counter >= items.length) {
return null;
}
String item = items[counter];
return item;
}
}
private static class SkipWriterStub implements ItemWriter<String> {
private List<String> written = new CopyOnWriteArrayList<>();
private Collection<String> failures = Collections.emptySet();
public List<String> getWritten() {
return written;
}
public void clear() {
written.clear();
}
@Override
public void write(List<? extends String> items) throws Exception {
for (String item : items) {
written.add(item);
checkFailure(item);
}
}
private void checkFailure(String item) {
if (failures.contains(item)) {
throw new RuntimeException("Planned failure");
}
}
}
private static class SkipProcessorStub implements ItemProcessor<String, String> {
private final Log logger = LogFactory.getLog(getClass());
private List<String> processed = new CopyOnWriteArrayList<>();
public List<String> getProcessed() {
return processed;
}
public void clear() {
processed.clear();
}
@Nullable
@Override
public String process(String item) throws Exception {
processed.add(item);
logger.debug("Processed item: "+item);
return item;
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2006-2019 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
*
* https://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.core.test.step;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.lang.Nullable;
/**
* @author Dave Syer
*
*/
public class SplitJobMapRepositoryIntegrationTests {
private static final int MAX_COUNT = 1000;
/** Logger */
private final Log logger = LogFactory.getLog(getClass());
@SuppressWarnings("resource")
@Test
public void testMultithreadedSplit() throws Throwable {
JobLauncher jobLauncher = null;
Job job = null;
ClassPathXmlApplicationContext context = null;
for (int i = 0; i < MAX_COUNT; i++) {
if (i % 100 == 0) {
if (context!=null) {
context.close();
}
logger.info("Starting job: " + i);
context = new ClassPathXmlApplicationContext(getClass().getSimpleName()
+ "-context.xml", getClass());
jobLauncher = context.getBean("jobLauncher", JobLauncher.class);
job = context.getBean("job", Job.class);
}
try {
JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().addLong("count", new Long(i))
.toJobParameters());
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
}
catch (Throwable e) {
logger.info("Failed on iteration " + i + " of " + MAX_COUNT);
throw e;
}
}
}
public static class CountingTasklet implements Tasklet {
private int maxCount = 10;
private AtomicInteger count = new AtomicInteger(0);
@Nullable
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
contribution.incrementReadCount();
contribution.incrementWriteCount(1);
return RepeatStatus.continueIf(count.incrementAndGet() < maxCount);
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2006-2014 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
*
* https://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.core.test.step;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.util.SerializationUtils;
/**
* @author Dave Syer
* @author Michael Minella
*/
public class StepExecutionSerializationUtilsTests {
@Test
public void testCycle() throws Exception {
StepExecution stepExecution = new StepExecution("step", new JobExecution(new JobInstance(123L,
"job"), 321L, new JobParameters(), null), 11L);
stepExecution.getExecutionContext().put("foo.bar.spam", 123);
StepExecution result = getCopy(stepExecution);
assertEquals(stepExecution, result);
}
@Test
public void testMultipleCycles() throws Throwable {
int count = 0;
int repeats = 100;
int threads = 10;
Executor executor = Executors.newFixedThreadPool(threads);
CompletionService<StepExecution> completionService = new ExecutorCompletionService<>(executor);
for (int i = 0; i < repeats; i++) {
final JobExecution jobExecution = new JobExecution(new JobInstance(123L, "job"), 321L, new JobParameters(), null);
for (int j = 0; j < threads; j++) {
completionService.submit(new Callable<StepExecution>() {
@Override
public StepExecution call() throws Exception {
final StepExecution stepExecution = jobExecution.createStepExecution("step");
stepExecution.getExecutionContext().put("foo.bar.spam", 123);
StepExecution result = getCopy(stepExecution);
assertEquals(stepExecution.getExecutionContext(), result.getExecutionContext());
return result;
}
});
}
for (int j = 0; j < threads; j++) {
Future<StepExecution> future = completionService.poll(repeats, TimeUnit.MILLISECONDS);
if (future != null) {
count++;
try {
future.get();
} catch (Throwable e) {
throw new IllegalStateException("Failed on count="+count, e);
}
}
}
}
while (count < threads*repeats) {
Future<StepExecution> future = completionService.poll();
count++;
try {
future.get();
} catch (Throwable e) {
throw new IllegalStateException("Failed on count="+count, e);
}
}
}
private StepExecution getCopy(StepExecution stepExecution) {
return (StepExecution) SerializationUtils.deserialize(SerializationUtils.serialize(stepExecution));
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2014 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
*
* https://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.core.test.timeout;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemWriter;
public class LoggingItemWriter<T> implements ItemWriter<T> {
protected Log logger = LogFactory.getLog(LoggingItemWriter.class);
@Override
public void write(List<? extends T> items) throws Exception {
logger.info(items);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2014-2019 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
*
* https://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.core.test.timeout;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.lang.Nullable;
public class SleepingItemProcessor<I> implements ItemProcessor<I, I> {
private long millisToSleep;
@Nullable
@Override
public I process(I item) throws Exception {
Thread.sleep(millisToSleep);
return item;
}
public void setMillisToSleep(long millisToSleep) {
this.millisToSleep = millisToSleep;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2014-2019 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
*
* https://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.core.test.timeout;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.lang.Nullable;
public class SleepingTasklet implements Tasklet {
private long millisToSleep;
@Nullable
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext) throws Exception {
Thread.sleep(millisToSleep);
return RepeatStatus.FINISHED;
}
public void setMillisToSleep(long millisToSleep) {
this.millisToSleep = millisToSleep;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2014 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
*
* https://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.core.test.timeout;
import javax.sql.DataSource;
import static org.junit.Assert.assertEquals;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.test.AbstractIntegrationTests;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/simple-job-launcher-context.xml", "/META-INF/batch/timeoutJob.xml"})
public class TimeoutJobIntegrationTests extends AbstractIntegrationTests {
/** Logger */
@SuppressWarnings("unused")
private final Log logger = LogFactory.getLog(getClass());
@Autowired
private JobLauncher jobLauncher;
@Autowired
@Qualifier("chunkTimeoutJob")
private Job chunkTimeoutJob;
@Autowired
@Qualifier("taskletTimeoutJob")
private Job taskletTimeoutJob;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Test
public void testChunkTimeoutShouldFail() throws Exception {
JobExecution execution = jobLauncher.run(chunkTimeoutJob, new JobParametersBuilder().addLong("id", System.currentTimeMillis())
.toJobParameters());
assertEquals(BatchStatus.FAILED, execution.getStatus());
}
@Test
public void testTaskletTimeoutShouldFail() throws Exception {
JobExecution execution = jobLauncher.run(taskletTimeoutJob, new JobParametersBuilder().addLong("id", System.currentTimeMillis())
.toJobParameters());
assertEquals(BatchStatus.FAILED, execution.getStatus());
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2009-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
*
* https://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 test.jdbc.datasource;
import java.io.File;
import javax.sql.DataSource;
import org.apache.derby.jdbc.EmbeddedDataSource;
import org.springframework.beans.factory.config.AbstractFactoryBean;
public class DerbyDataSourceFactoryBean extends AbstractFactoryBean<DataSource> {
private String dataDirectory = "build/derby-home";
public void setDataDirectory(String dataDirectory) {
this.dataDirectory = dataDirectory;
}
@Override
protected DataSource createInstance() throws Exception {
File directory = new File(dataDirectory);
System.setProperty("derby.system.home", directory.getCanonicalPath());
System.setProperty("derby.storage.fileSyncTransactionLog", "true");
System.setProperty("derby.storage.pageCacheSize", "100");
final EmbeddedDataSource ds = new EmbeddedDataSource();
ds.setDatabaseName("build/derbydb");
ds.setCreateDatabase("create");
return ds;
}
@Override
public Class<DataSource> getObjectType() {
return DataSource.class;
}
}