├── README ├── build.gradle ├── gradle.properties ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── springframework │ │ ├── batch │ │ └── mongo │ │ │ ├── config │ │ │ ├── ApplicationConfiguration.java │ │ │ ├── Database.java │ │ │ ├── MongoDbFactoryBean.java │ │ │ └── MongoDbInitializer.java │ │ │ └── dao │ │ │ ├── AbstractMongoDao.java │ │ │ ├── MongoExecutionContextDao.java │ │ │ ├── MongoJobExecutionDao.java │ │ │ ├── MongoJobInstanceDao.java │ │ │ └── MongoStepExecutionDao.java │ │ └── javaconfig │ │ └── util │ │ └── ConfigurationSupport.java └── resources │ ├── application-config.xml │ ├── batch.properties │ └── logback.xml └── test └── java └── org └── springframework └── batch └── mongo ├── config ├── ApplicationTestConfiguration.java └── JobConfiguration.java ├── dao ├── AbstractExecutionContextDaoTests.java ├── AbstractJobDaoTests.java ├── AbstractJobExecutionDaoTests.java ├── AbstractJobInstanceDaoTests.java ├── AbstractStepExecutionDaoTests.java ├── MongoDaoTests.java ├── MongoExecutionContextDaoTests.java ├── MongoJobDaoTests.java ├── MongoJobExecutionDaoTests.java ├── MongoJobInstanceDaoTests.java └── MongoStepExecutionDaoTests.java ├── example ├── ExampleItemReader.java ├── ExampleItemReaderTests.java ├── ExampleItemWriter.java ├── ExampleItemWriterTests.java └── ExampleJobConfigurationTests.java ├── step └── StepSupport.java └── util └── Shutdown.java /README: -------------------------------------------------------------------------------- 1 | Spring Batch meta-data persistency DAOs implementation over MongoDB. For more details please see http://jbaruch.wordpress.com/2010/04/27/integrating-mongodb-with-spring-batch/ 2 | 3 | Here's how: 4 | 1. Get MongoDB - http://www.mongodb.org/display/DOCS/Downloads 5 | 2. Get Gradle - http://dist.codehaus.org/gradle 6 | 3. (Optional) Set mongoDB home and startMongo flag in gradle.properties. If you do so, database will automatically start before tests and shutdown after. If you're on *nix and installed MongoDB distribution, it will be found without setting MongoDB home. 7 | 4. Run 'gradle test assemble' -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | 4 | group = 'org.springframework.batch' 5 | version = '1.0-SNAPSHOT' 6 | 7 | configurations.compile.transitive = true 8 | 9 | sourceCompatibility = 1.5 10 | targetCompatibility = 1.5 11 | 12 | repositories { 13 | mavenRepo urls: ["http://repo.jfrog.org/artifactory/libs-releases"] 14 | mavenRepo urls: ["http://repo.jfrog.org/artifactory/libs-snapshots"] 15 | } 16 | dependencies { 17 | compile "org.slf4j:slf4j-api:1.5.11" 18 | compile "ch.qos.logback:logback-classic:0.9.18" 19 | compile "org.slf4j:log4j-over-slf4j:1.5.11" 20 | compile "org.slf4j:jcl-over-slf4j:1.5.11" 21 | compile "cglib:cglib:2.2" 22 | compile "org.springframework:org.springframework.core:3.0.2.RELEASE" 23 | compile "org.springframework:org.springframework.aop:3.0.2.RELEASE" 24 | compile "org.springframework:org.springframework.context:3.0.2.RELEASE" 25 | compile "org.springframework:org.springframework.transaction:3.0.2.RELEASE" 26 | compile "org.aspectj:aspectjrt:1.6.8" 27 | compile "org.aspectj:aspectjweaver:1.6.8" 28 | compile "org.springframework:org.springframework.beans:3.0.2.RELEASE" 29 | compile "org.springframework:org.springframework.asm:3.0.2.RELEASE" 30 | compile "org.springframework:org.springframework.expression:3.0.2.RELEASE" 31 | compile("org.springframework.batch:spring-batch-core:2.1.2.RELEASE") { 32 | exclude(module: 'spring-tx') 33 | exclude(module: 'spring-beans') 34 | exclude(module: 'spring-context') 35 | exclude(module: 'spring-aop') 36 | exclude(module: 'spring-core') 37 | } 38 | compile("org.springframework.batch:spring-batch-infrastructure:2.1.2.RELEASE") { 39 | exclude(module: 'spring-core') 40 | exclude(module: 'commons-logging') 41 | } 42 | compile "org.mongodb:mongo-java-driver:2.3" 43 | compile "commons-io:commons-io:1.4" 44 | testCompile "junit:junit:4.8.1" 45 | testCompile "org.springframework:org.springframework.test:3.0.2.RELEASE" 46 | testCompile("org.springframework.batch:spring-batch-test:2.1.2.RELEASE") { 47 | exclude(module: 'spring-test') 48 | exclude(module: 'spring-jdbc') 49 | exclude(module: 'commons-io') 50 | exclude(module: 'commons-collections') 51 | } 52 | } 53 | 54 | task replacePoms(dependsOn: install) << { 55 | def pomsDir = new File(project.buildDir, "poms") 56 | def defaultPomName = "pom-default.xml" 57 | def defaultPom = new File(pomsDir, defaultPomName) 58 | if (defaultPom.exists()) { 59 | File pomFile = new File(project.projectDir, "pom.xml") 60 | if (pomFile.exists()) { 61 | pomFile.renameTo(new File(project.projectDir, "pom.xml.bak")) 62 | } 63 | project.copy { 64 | from(pomsDir) { 65 | include defaultPomName 66 | rename defaultPomName, 'pom.xml' 67 | } 68 | into(project.projectDir) 69 | } 70 | } 71 | } 72 | 73 | test.doFirst { 74 | if (project.hasProperty('startMongo') && project.startMongo) { 75 | def windows = ((String) System.properties['os.name']).toLowerCase().contains("win") 76 | File executable 77 | project.mongoExecFound = false 78 | if (project.hasProperty('mongoHome')) { 79 | executable = new File(project.mongoHome + "/bin/" + "mongod" + (windows ? ".exe" : "")) 80 | project.mongoExecFound = executable.isFile(); 81 | } 82 | if (!project.mongoExecFound && !windows) { 83 | executable = new File('/usr/bin/mongod'); 84 | project.mongoExecFound = executable.isFile(); 85 | } 86 | if (project.mongoExecFound) { 87 | logger.info("Going to startup MongoDB daemon: ${executable} ...") 88 | ant.exec(spawn: true, executable: executable.getAbsolutePath()) 89 | } 90 | } 91 | } 92 | 93 | test.doLast { 94 | if (project.mongoExecFound) { 95 | logger.info('taking MongoDB down...') 96 | //Shutdown the db. Can't just call the class since script's classpath can't use project classes 97 | ant.java(classname: 'org.springframework.batch.mongo.util.Shutdown', 98 | fork: true, 99 | classpath: "${sourceSets.test.runtimeClasspath.asPath}") 100 | } 101 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #MongoDB installation directory. 2 | #Mandatory for Windows (if you want the test to start MongoDB), on *Nix will search in ... if not set. 3 | mongoHome=C:/Users/JBaruch/lib/mongodb-win32-i386-1.4.1 4 | 5 | #Try to start MongoDB before tests and shut it down after. 6 | startMongo=true -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | org.springframework.batch 7 | spring-batch-over-mongodb 8 | 1.0-SNAPSHOT 9 | jar 10 | Spring Batch over MongoDB 11 | Use Gradle for build. This pom is provided for IDE integration only. 12 | 13 | http://github.com/jbaruch/springbatch-over-mongodb 14 | 15 | 16 | 3.0.2.RELEASE 17 | 2.1.0.RELEASE 18 | 1.6.8 19 | 1.5.11 20 | 21 | 22 | 23 | 24 | org.slf4j 25 | slf4j-api 26 | ${slf4j.version} 27 | 28 | 29 | ch.qos.logback 30 | logback-classic 31 | 0.9.18 32 | 33 | 34 | org.slf4j 35 | log4j-over-slf4j 36 | ${slf4j.version} 37 | 38 | 39 | org.slf4j 40 | jcl-over-slf4j 41 | ${slf4j.version} 42 | 43 | 44 | 45 | 46 | junit 47 | junit 48 | 4.8.1 49 | test 50 | 51 | 52 | org.springframework 53 | org.springframework.test 54 | ${spring.framework.version} 55 | test 56 | 57 | 58 | org.springframework.batch 59 | spring-batch-test 60 | ${spring.batch.version} 61 | test 62 | 63 | 64 | org.springframework 65 | spring-test 66 | 67 | 68 | org.springframework 69 | spring-jdbc 70 | 71 | 72 | commons-io 73 | commons-io 74 | 75 | 76 | commons-collections 77 | commons-collections 78 | 79 | 80 | 81 | 82 | 83 | 84 | cglib 85 | cglib 86 | 2.2 87 | 88 | 89 | org.springframework 90 | org.springframework.core 91 | ${spring.framework.version} 92 | 93 | 94 | org.springframework 95 | org.springframework.aop 96 | ${spring.framework.version} 97 | 98 | 99 | org.springframework 100 | org.springframework.context 101 | ${spring.framework.version} 102 | 103 | 104 | org.springframework 105 | org.springframework.transaction 106 | ${spring.framework.version} 107 | 108 | 109 | org.aspectj 110 | aspectjrt 111 | ${aspectj.version} 112 | 113 | 114 | org.aspectj 115 | aspectjweaver 116 | ${aspectj.version} 117 | 118 | 119 | org.springframework 120 | org.springframework.beans 121 | 3.0.2.RELEASE 122 | 123 | 124 | org.springframework 125 | org.springframework.asm 126 | 3.0.2.RELEASE 127 | 128 | 129 | org.springframework 130 | org.springframework.expression 131 | 3.0.2.RELEASE 132 | 133 | 134 | 135 | 136 | org.springframework.batch 137 | spring-batch-core 138 | ${spring.batch.version} 139 | 140 | 141 | org.springframework 142 | spring-tx 143 | 144 | 145 | org.springframework 146 | spring-beans 147 | 148 | 149 | org.springframework 150 | spring-context 151 | 152 | 153 | org.springframework 154 | spring-aop 155 | 156 | 157 | org.springframework 158 | spring-core 159 | 160 | 161 | 162 | 163 | org.springframework.batch 164 | spring-batch-infrastructure 165 | ${spring.batch.version} 166 | 167 | 168 | org.springframework 169 | spring-core 170 | 171 | 172 | commons-logging 173 | commons-logging 174 | 175 | 176 | 177 | 178 | 179 | 180 | org.mongodb 181 | mongo-java-driver 182 | 2.3 183 | 184 | 185 | 186 | 187 | commons-io 188 | commons-io 189 | 1.4 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/config/ApplicationConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.config; 2 | 3 | import com.mongodb.DB; 4 | import com.mongodb.Mongo; 5 | import org.springframework.batch.core.JobParametersIncrementer; 6 | import org.springframework.batch.core.configuration.JobRegistry; 7 | import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; 8 | import org.springframework.batch.core.configuration.support.MapJobRegistry; 9 | import org.springframework.batch.core.explore.JobExplorer; 10 | import org.springframework.batch.core.explore.support.SimpleJobExplorer; 11 | import org.springframework.batch.core.launch.support.RunIdIncrementer; 12 | import org.springframework.batch.core.launch.support.SimpleJobLauncher; 13 | import org.springframework.batch.core.launch.support.SimpleJobOperator; 14 | import org.springframework.batch.core.repository.JobRepository; 15 | import org.springframework.batch.core.repository.dao.ExecutionContextDao; 16 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 17 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 18 | import org.springframework.batch.core.repository.dao.StepExecutionDao; 19 | import org.springframework.batch.core.repository.support.SimpleJobRepository; 20 | import org.springframework.batch.support.transaction.ResourcelessTransactionManager; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.beans.factory.annotation.Value; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.core.task.TaskExecutor; 26 | import org.springframework.javaconfig.util.ConfigurationSupport; 27 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 28 | import org.springframework.transaction.PlatformTransactionManager; 29 | 30 | import java.net.UnknownHostException; 31 | 32 | /** 33 | * Created by IntelliJ IDEA. 34 | * 35 | * @author JBaruch 36 | * @since 19-Apr-2010 37 | */ 38 | @Configuration 39 | public class ApplicationConfiguration { 40 | 41 | @Value("${app.db.name}") 42 | private String appDbName; 43 | 44 | @Value("${batch.db.name}") 45 | private String batchDbName; 46 | 47 | @Value("${step.thread.core.pool.size}") 48 | private int corePoolSize; 49 | 50 | @Value("${step.thread.max.pool.size}") 51 | private int maxPoolSize; 52 | 53 | 54 | @Autowired 55 | private ConfigurationSupport configurationSupport; 56 | 57 | @Autowired 58 | private ExecutionContextDao executionContextDao; 59 | 60 | @Autowired 61 | private JobExecutionDao jobExecutionDao; 62 | 63 | @Autowired 64 | private JobInstanceDao jobInstanceDao; 65 | 66 | @Autowired 67 | private StepExecutionDao stepExecutionDao; 68 | 69 | public static final String DOT_ESCAPE_STRING = "\\{dot\\}"; 70 | public static final String DOT_STRING = "\\."; 71 | 72 | 73 | @Bean 74 | public SimpleJobOperator jobOperator() { 75 | SimpleJobOperator jobOperator = new SimpleJobOperator(); 76 | jobOperator.setJobLauncher(jobLauncher()); 77 | jobOperator.setJobExplorer(jobExplorer()); 78 | jobOperator.setJobRepository(jobRepository()); 79 | jobOperator.setJobRegistry(jobRegistry()); 80 | return jobOperator; 81 | } 82 | 83 | @Bean 84 | public JobExplorer jobExplorer() { 85 | return new SimpleJobExplorer(jobInstanceDao, jobExecutionDao, stepExecutionDao, executionContextDao); 86 | } 87 | 88 | @Bean 89 | public JobRegistry jobRegistry() { 90 | return new MapJobRegistry(); 91 | } 92 | 93 | @Bean 94 | JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() { 95 | JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor(); 96 | postProcessor.setJobRegistry(jobRegistry()); 97 | return postProcessor; 98 | } 99 | 100 | @Bean 101 | public SimpleJobLauncher jobLauncher() { 102 | SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); 103 | jobLauncher.setJobRepository(jobRepository()); 104 | return jobLauncher; 105 | } 106 | 107 | @Bean 108 | public JobRepository jobRepository() { 109 | return new SimpleJobRepository(jobInstanceDao, jobExecutionDao, stepExecutionDao, executionContextDao); 110 | } 111 | 112 | @Bean 113 | public TaskExecutor taskExecutor() { 114 | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 115 | taskExecutor.setCorePoolSize(corePoolSize); 116 | taskExecutor.setMaxPoolSize(maxPoolSize); 117 | return taskExecutor; 118 | } 119 | 120 | @Bean 121 | public JobParametersIncrementer jobParametersIncrementer() { 122 | return new RunIdIncrementer(); 123 | } 124 | 125 | @Bean 126 | @Database(Database.Purpose.APPLICATION) 127 | public DB applicationDb() throws UnknownHostException { 128 | return mongo().getDB(appDbName); 129 | } 130 | 131 | @Bean 132 | @Database(Database.Purpose.BATCH) 133 | public DB batchDb() throws UnknownHostException { 134 | return mongo().getDB(batchDbName); 135 | } 136 | 137 | @Bean 138 | public Mongo mongo() throws UnknownHostException { 139 | return new Mongo(); 140 | } 141 | 142 | @Bean 143 | public PlatformTransactionManager transactionManager() { 144 | return new ResourcelessTransactionManager(); 145 | } 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/config/Database.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.config; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | /** 8 | * Created by IntelliJ IDEA. 9 | * 10 | * @author Baruch S. 11 | * @since Apr 18, 2010 12 | */ 13 | @java.lang.annotation.Documented 14 | @java.lang.annotation.Retention(RetentionPolicy.RUNTIME) 15 | @Qualifier 16 | public @interface Database { 17 | Purpose value(); 18 | 19 | public enum Purpose { 20 | BATCH, APPLICATION 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/config/MongoDbFactoryBean.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.config; 2 | 3 | import com.mongodb.DB; 4 | import com.mongodb.Mongo; 5 | import org.springframework.beans.factory.FactoryBean; 6 | import org.springframework.beans.factory.InitializingBean; 7 | 8 | import java.net.UnknownHostException; 9 | 10 | /** 11 | * com.mongodb.DB object factory bean for usage in Spring xml configuration. 12 | * Created by IntelliJ IDEA. 13 | * 14 | * @author Baruch S. 15 | * @since Apr 28, 2010 16 | */ 17 | public class MongoDbFactoryBean implements FactoryBean, InitializingBean { 18 | 19 | private String dbName; 20 | private Mongo mongo; 21 | private DB db; 22 | 23 | public MongoDbFactoryBean(String dbName, String mongoHost, int mongoPort) throws UnknownHostException { 24 | this.dbName = dbName; 25 | this.mongo = new Mongo(mongoHost, mongoPort); 26 | } 27 | 28 | public Object getObject() throws Exception { 29 | return db; 30 | } 31 | 32 | public Class getObjectType() { 33 | return DB.class; 34 | } 35 | 36 | public boolean isSingleton() { 37 | return true; 38 | } 39 | 40 | public void afterPropertiesSet() throws Exception { 41 | db = mongo.getDB(dbName); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/config/MongoDbInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006-2007 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.batch.mongo.config; 18 | 19 | import com.mongodb.DB; 20 | import org.apache.commons.logging.Log; 21 | import org.apache.commons.logging.LogFactory; 22 | import org.springframework.beans.factory.InitializingBean; 23 | import org.springframework.util.Assert; 24 | 25 | public class MongoDbInitializer implements InitializingBean { 26 | 27 | private boolean initialize = false; 28 | 29 | private DB db; 30 | 31 | private Log logger = LogFactory.getLog(getClass()); 32 | 33 | public void setInitialize(boolean initialize) { 34 | this.initialize = initialize; 35 | } 36 | 37 | public void setDb(DB db) { 38 | this.db = db; 39 | } 40 | 41 | public void afterPropertiesSet() throws Exception { 42 | Assert.notNull(db); 43 | if (initialize) { 44 | try { 45 | db.dropDatabase(); 46 | logger.debug("Database dropped"); 47 | } 48 | catch (Exception e) { 49 | logger.debug("Could not drop database", e); 50 | } 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/dao/AbstractMongoDao.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.BasicDBObject; 4 | import com.mongodb.DB; 5 | import com.mongodb.DBCollection; 6 | import com.mongodb.DBObject; 7 | import org.springframework.batch.mongo.config.Database; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | /** 11 | * Created by IntelliJ IDEA. 12 | * 13 | * @author Baruch S. 14 | * @since Apr 15, 2010 15 | */ 16 | public abstract class AbstractMongoDao { 17 | 18 | protected DB db; 19 | protected static final String UPDATED_EXISTING_STATUS = "updatedExisting"; 20 | protected static final String VERSION_KEY = "version"; 21 | protected static final String START_TIME_KEY = "startTime"; 22 | protected static final String END_TIME_KEY = "endTime"; 23 | protected static final String EXIT_CODE_KEY = "exitCode"; 24 | protected static final String EXIT_MESSAGE_KEY = "exitMessage"; 25 | protected static final String LAST_UPDATED_KEY = "lastUpdated"; 26 | protected static final String STATUS_KEY = "status"; 27 | protected static final String SEQUENCES_COLLECTION_NAME = "Sequences"; 28 | public static final String ID_KEY = "_id"; 29 | public static final String NS_KEY = "_ns"; 30 | public static final String DOT_ESCAPE_STRING = "\\{dot\\}"; 31 | public static final String DOT_STRING = "\\."; 32 | 33 | @Autowired 34 | @Database(Database.Purpose.BATCH) 35 | public void setDb(DB db) { 36 | this.db = db; 37 | } 38 | 39 | protected abstract DBCollection getCollection(); 40 | 41 | protected Long getNextId(String name) { 42 | DBCollection collection = db.getCollection(SEQUENCES_COLLECTION_NAME); 43 | BasicDBObject sequence = new BasicDBObject("name", name); 44 | collection.update(sequence, new BasicDBObject("$inc", new BasicDBObject("value", 1L)), true, false); 45 | return (Long) collection.findOne(sequence).get("value"); 46 | } 47 | 48 | protected void removeSystemFields(DBObject dbObject) { 49 | dbObject.removeField(ID_KEY); 50 | dbObject.removeField(NS_KEY); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/dao/MongoExecutionContextDao.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.BasicDBObject; 4 | import com.mongodb.BasicDBObjectBuilder; 5 | import com.mongodb.DBCollection; 6 | import com.mongodb.DBObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.batch.core.JobExecution; 10 | import org.springframework.batch.core.StepExecution; 11 | import org.springframework.batch.core.repository.dao.ExecutionContextDao; 12 | import org.springframework.batch.item.ExecutionContext; 13 | import org.springframework.stereotype.Repository; 14 | import org.springframework.util.Assert; 15 | import org.springframework.util.NumberUtils; 16 | 17 | import javax.annotation.PostConstruct; 18 | import java.math.BigDecimal; 19 | import java.math.BigInteger; 20 | import java.util.Map; 21 | 22 | /** 23 | * Created by IntelliJ IDEA. 24 | * 25 | * @author Baruch S. 26 | * @since Apr 15, 2010 27 | */ 28 | @Repository 29 | public class MongoExecutionContextDao extends AbstractMongoDao implements ExecutionContextDao { 30 | private static final String STEP_EXECUTION_ID_KEY = "stepExecutionId"; 31 | private static final String JOB_EXECUTION_ID_KEY = "jobExecutionId"; 32 | protected static final String TYPE_SUFFIX = "_TYPE"; 33 | private static final Logger LOG = LoggerFactory.getLogger(MongoExecutionContextDao.class); 34 | 35 | @PostConstruct 36 | public void init() { 37 | getCollection().ensureIndex(BasicDBObjectBuilder.start().add(STEP_EXECUTION_ID_KEY, 1).add(JOB_EXECUTION_ID_KEY, 1).get()); 38 | } 39 | 40 | public ExecutionContext getExecutionContext(JobExecution jobExecution) { 41 | return getExecutionContext(JOB_EXECUTION_ID_KEY, jobExecution.getId()); 42 | } 43 | 44 | public ExecutionContext getExecutionContext(StepExecution stepExecution) { 45 | return getExecutionContext(STEP_EXECUTION_ID_KEY, stepExecution.getId()); 46 | } 47 | 48 | public void saveExecutionContext(JobExecution jobExecution) { 49 | saveOrUpdateExecutionContext(JOB_EXECUTION_ID_KEY, jobExecution.getId(), jobExecution.getExecutionContext()); 50 | } 51 | 52 | public void saveExecutionContext(StepExecution stepExecution) { 53 | saveOrUpdateExecutionContext(STEP_EXECUTION_ID_KEY, stepExecution.getId(), stepExecution.getExecutionContext()); 54 | } 55 | 56 | public void updateExecutionContext(JobExecution jobExecution) { 57 | saveOrUpdateExecutionContext(JOB_EXECUTION_ID_KEY, jobExecution.getId(), jobExecution.getExecutionContext()); 58 | } 59 | 60 | public void updateExecutionContext(StepExecution stepExecution) { 61 | saveOrUpdateExecutionContext(STEP_EXECUTION_ID_KEY, stepExecution.getId(), stepExecution.getExecutionContext()); 62 | } 63 | 64 | private void saveOrUpdateExecutionContext(String executionIdKey, Long executionId, ExecutionContext executionContext) { 65 | Assert.notNull(executionId, "ExecutionId must not be null."); 66 | Assert.notNull(executionContext, "The ExecutionContext must not be null."); 67 | 68 | DBObject dbObject = new BasicDBObject(executionIdKey, executionId); 69 | for (Map.Entry entry : executionContext.entrySet()) { 70 | Object value = entry.getValue(); 71 | String key = entry.getKey(); 72 | dbObject.put(key, value); 73 | if (value instanceof BigDecimal || value instanceof BigInteger) { 74 | dbObject.put(key + TYPE_SUFFIX, value.getClass().getName()); 75 | } 76 | } 77 | getCollection().update(new BasicDBObject(executionIdKey, executionId), dbObject, true, false); 78 | } 79 | 80 | @SuppressWarnings({"unchecked"}) 81 | private ExecutionContext getExecutionContext(String executionIdKey, Long executionId) { 82 | Assert.notNull(executionId, "ExecutionId must not be null."); 83 | DBObject result = getCollection().findOne(new BasicDBObject(executionIdKey, executionId)); 84 | ExecutionContext executionContext = new ExecutionContext(); 85 | if (result != null) { 86 | result.removeField(executionIdKey); 87 | removeSystemFields(result); 88 | for (String key : result.keySet()) { 89 | Object value = result.get(key); 90 | String type = (String) result.get(key + TYPE_SUFFIX); 91 | if (type != null && Number.class.isAssignableFrom(value.getClass())) { 92 | try { 93 | value = NumberUtils.convertNumberToTargetClass((Number) value, (Class) Class.forName(type)); 94 | } catch (Exception e) { 95 | LOG.warn("Failed to convert {} to {}", key, type); 96 | } 97 | } 98 | executionContext.put(key, value); 99 | } 100 | } 101 | return executionContext; 102 | } 103 | 104 | protected DBCollection getCollection() { 105 | return db.getCollection(ExecutionContext.class.getSimpleName()); 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/dao/MongoJobExecutionDao.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.*; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.batch.core.BatchStatus; 7 | import org.springframework.batch.core.ExitStatus; 8 | import org.springframework.batch.core.JobExecution; 9 | import org.springframework.batch.core.JobInstance; 10 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 11 | import org.springframework.batch.core.repository.dao.NoSuchObjectException; 12 | import org.springframework.dao.OptimisticLockingFailureException; 13 | import org.springframework.stereotype.Repository; 14 | import org.springframework.util.Assert; 15 | 16 | import javax.annotation.PostConstruct; 17 | import java.util.*; 18 | 19 | import static com.mongodb.BasicDBObjectBuilder.start; 20 | import static org.springframework.batch.mongo.dao.MongoJobInstanceDao.JOB_INSTANCE_ID_KEY; 21 | import static org.springframework.batch.mongo.dao.MongoJobInstanceDao.jobInstanceIdObj; 22 | 23 | /** 24 | * Created by IntelliJ IDEA. 25 | * 26 | * @author Baruch S. 27 | * @since Apr 15, 2010 28 | */ 29 | @Repository 30 | public class MongoJobExecutionDao extends AbstractMongoDao implements JobExecutionDao { 31 | 32 | public static final String JOB_EXECUTION_ID_KEY = "jobExecutionId"; 33 | private static final String CREATE_TIME_KEY = "createTime"; 34 | private static final Logger LOG = LoggerFactory.getLogger(MongoJobExecutionDao.class); 35 | 36 | @PostConstruct 37 | public void init() { 38 | getCollection().ensureIndex(BasicDBObjectBuilder.start().add(JOB_EXECUTION_ID_KEY, 1).add(JOB_INSTANCE_ID_KEY, 1).get()); 39 | } 40 | 41 | public void saveJobExecution(JobExecution jobExecution) { 42 | validateJobExecution(jobExecution); 43 | jobExecution.incrementVersion(); 44 | Long id = getNextId(JobExecution.class.getSimpleName()); 45 | save(jobExecution, id); 46 | } 47 | 48 | private void save(JobExecution jobExecution, Long id) { 49 | jobExecution.setId(id); 50 | DBObject object = toDbObjectWithoutVersion(jobExecution); 51 | object.put(VERSION_KEY, jobExecution.getVersion()); 52 | getCollection().save(object); 53 | } 54 | 55 | private DBObject toDbObjectWithoutVersion(JobExecution jobExecution) { 56 | return start() 57 | .add(JOB_EXECUTION_ID_KEY, jobExecution.getId()) 58 | .add(JOB_INSTANCE_ID_KEY, jobExecution.getJobId()) 59 | .add(START_TIME_KEY, jobExecution.getStartTime()) 60 | .add(END_TIME_KEY, jobExecution.getEndTime()) 61 | .add(STATUS_KEY, jobExecution.getStatus().toString()) 62 | .add(EXIT_CODE_KEY, jobExecution.getExitStatus().getExitCode()) 63 | .add(EXIT_MESSAGE_KEY, jobExecution.getExitStatus().getExitDescription()) 64 | .add(CREATE_TIME_KEY, jobExecution.getCreateTime()) 65 | .add(LAST_UPDATED_KEY, jobExecution.getLastUpdated()).get(); 66 | } 67 | 68 | 69 | private void validateJobExecution(JobExecution jobExecution) { 70 | 71 | Assert.notNull(jobExecution); 72 | Assert.notNull(jobExecution.getJobId(), "JobExecution Job-Id cannot be null."); 73 | Assert.notNull(jobExecution.getStatus(), "JobExecution status cannot be null."); 74 | Assert.notNull(jobExecution.getCreateTime(), "JobExecution create time cannot be null"); 75 | } 76 | 77 | public synchronized void updateJobExecution(JobExecution jobExecution) { 78 | validateJobExecution(jobExecution); 79 | 80 | Long jobExecutionId = jobExecution.getId(); 81 | Assert.notNull(jobExecutionId, 82 | "JobExecution ID cannot be null. JobExecution must be saved before it can be updated"); 83 | 84 | Assert.notNull(jobExecution.getVersion(), 85 | "JobExecution version cannot be null. JobExecution must be saved before it can be updated"); 86 | 87 | Integer version = jobExecution.getVersion() + 1; 88 | 89 | if (getCollection().findOne(jobExecutionIdObj(jobExecutionId)) == null) { 90 | throw new NoSuchObjectException("Invalid JobExecution, ID " + jobExecutionId + " not found."); 91 | } 92 | 93 | DBObject object = toDbObjectWithoutVersion(jobExecution); 94 | object.put(VERSION_KEY, version); 95 | getCollection().update(start() 96 | .add(JOB_EXECUTION_ID_KEY, jobExecutionId) 97 | .add(VERSION_KEY, jobExecution.getVersion()).get(), 98 | object); 99 | 100 | // Avoid concurrent modifications... 101 | DBObject lastError = db.getLastError(); 102 | if (!((Boolean) lastError.get(UPDATED_EXISTING_STATUS))) { 103 | LOG.error("Update returned status {}", lastError); 104 | DBObject existingJobExecution = getCollection().findOne(jobExecutionIdObj(jobExecutionId), new BasicDBObject(VERSION_KEY, 1)); 105 | if (existingJobExecution == null) { 106 | throw new IllegalArgumentException("Can't update this jobExecution, it was never saved."); 107 | } 108 | Integer curentVersion = ((Integer) existingJobExecution.get(VERSION_KEY)); 109 | throw new OptimisticLockingFailureException("Attempt to update job execution id=" 110 | + jobExecutionId + " with wrong version (" + jobExecution.getVersion() 111 | + "), where current version is " + curentVersion); 112 | } 113 | 114 | jobExecution.incrementVersion(); 115 | } 116 | 117 | public List findJobExecutions(JobInstance jobInstance) { 118 | Assert.notNull(jobInstance, "Job cannot be null."); 119 | Long id = jobInstance.getId(); 120 | Assert.notNull(id, "Job Id cannot be null."); 121 | DBCursor dbCursor = getCollection().find(jobInstanceIdObj(id)).sort(new BasicDBObject(JOB_EXECUTION_ID_KEY, -1)); 122 | List result = new ArrayList(); 123 | while (dbCursor.hasNext()) { 124 | DBObject dbObject = dbCursor.next(); 125 | result.add(mapJobExecution(jobInstance, dbObject)); 126 | } 127 | return result; 128 | } 129 | 130 | public JobExecution getLastJobExecution(JobInstance jobInstance) { 131 | Long id = jobInstance.getId(); 132 | 133 | DBCursor dbCursor = getCollection().find(jobInstanceIdObj(id)).sort(new BasicDBObject(CREATE_TIME_KEY, -1)).limit(1); 134 | if (!dbCursor.hasNext()) { 135 | return null; 136 | } else { 137 | DBObject singleResult = dbCursor.next(); 138 | if (dbCursor.hasNext()) { 139 | throw new IllegalStateException("There must be at most one latest job execution"); 140 | } 141 | return mapJobExecution(jobInstance, singleResult); 142 | } 143 | } 144 | 145 | public Set findRunningJobExecutions(String jobName) { 146 | DBCursor instancesCursor = db.getCollection(JobInstance.class.getSimpleName()).find(new BasicDBObject(MongoJobInstanceDao.JOB_NAME_KEY, jobName), jobInstanceIdObj(1L)); 147 | List ids = new ArrayList(); 148 | while (instancesCursor.hasNext()) { 149 | ids.add((Long) instancesCursor.next().get(JOB_INSTANCE_ID_KEY)); 150 | } 151 | 152 | DBCursor dbCursor = getCollection().find(BasicDBObjectBuilder.start() 153 | .add(JOB_INSTANCE_ID_KEY, new BasicDBObject("$in", ids.toArray())) 154 | .add(END_TIME_KEY, null).get()).sort(jobExecutionIdObj(-1L)); 155 | Set result = new HashSet(); 156 | while (dbCursor.hasNext()) { 157 | result.add(mapJobExecution(dbCursor.next())); 158 | } 159 | return result; 160 | } 161 | 162 | public JobExecution getJobExecution(Long executionId) { 163 | return mapJobExecution(getCollection().findOne(jobExecutionIdObj(executionId))); 164 | } 165 | 166 | public void synchronizeStatus(JobExecution jobExecution) { 167 | Long id = jobExecution.getId(); 168 | DBObject jobExecutionObject = getCollection().findOne(jobExecutionIdObj(id)); 169 | int currentVersion = jobExecutionObject != null ? ((Integer) jobExecutionObject.get(VERSION_KEY)) : 0; 170 | if (currentVersion != jobExecution.getVersion()) { 171 | if (jobExecutionObject == null) { 172 | save(jobExecution, id); 173 | jobExecutionObject = getCollection().findOne(jobExecutionIdObj(id)); 174 | } 175 | String status = (String) jobExecutionObject.get(STATUS_KEY); 176 | jobExecution.upgradeStatus(BatchStatus.valueOf(status)); 177 | jobExecution.setVersion(currentVersion); 178 | } 179 | } 180 | 181 | static BasicDBObject jobExecutionIdObj(Long id) { 182 | return new BasicDBObject(JOB_EXECUTION_ID_KEY, id); 183 | } 184 | 185 | @Override 186 | protected DBCollection getCollection() { 187 | return db.getCollection(JobExecution.class.getSimpleName()); 188 | } 189 | 190 | private JobExecution mapJobExecution(DBObject dbObject) { 191 | return mapJobExecution(null, dbObject); 192 | } 193 | 194 | private JobExecution mapJobExecution(JobInstance jobInstance, DBObject dbObject) { 195 | if (dbObject == null) { 196 | return null; 197 | } 198 | Long id = (Long) dbObject.get(JOB_EXECUTION_ID_KEY); 199 | JobExecution jobExecution; 200 | 201 | if (jobInstance == null) { 202 | jobExecution = new JobExecution(id); 203 | } else { 204 | jobExecution = new JobExecution(jobInstance, id); 205 | } 206 | jobExecution.setStartTime((Date) dbObject.get(START_TIME_KEY)); 207 | jobExecution.setEndTime((Date) dbObject.get(END_TIME_KEY)); 208 | jobExecution.setStatus(BatchStatus.valueOf((String) dbObject.get(STATUS_KEY))); 209 | jobExecution.setExitStatus(new ExitStatus(((String) dbObject.get(EXIT_CODE_KEY)), (String) dbObject.get(EXIT_MESSAGE_KEY))); 210 | jobExecution.setCreateTime((Date) dbObject.get(CREATE_TIME_KEY)); 211 | jobExecution.setLastUpdated((Date) dbObject.get(LAST_UPDATED_KEY)); 212 | jobExecution.setVersion((Integer) dbObject.get(VERSION_KEY)); 213 | return jobExecution; 214 | } 215 | 216 | 217 | } 218 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/dao/MongoJobInstanceDao.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.BasicDBObject; 4 | import com.mongodb.DBCollection; 5 | import com.mongodb.DBCursor; 6 | import com.mongodb.DBObject; 7 | import org.springframework.batch.core.JobExecution; 8 | import org.springframework.batch.core.JobInstance; 9 | import org.springframework.batch.core.JobParameter; 10 | import org.springframework.batch.core.JobParameters; 11 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 12 | import org.springframework.stereotype.Repository; 13 | import org.springframework.util.Assert; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.io.UnsupportedEncodingException; 17 | import java.math.BigInteger; 18 | import java.security.MessageDigest; 19 | import java.security.NoSuchAlgorithmException; 20 | import java.util.*; 21 | 22 | import static com.mongodb.BasicDBObjectBuilder.start; 23 | 24 | /** 25 | * Created by IntelliJ IDEA. 26 | * 27 | * @author Baruch S. 28 | * @since Apr 15, 2010 29 | */ 30 | @Repository 31 | public class MongoJobInstanceDao extends AbstractMongoDao implements JobInstanceDao { 32 | 33 | public static final String JOB_NAME_KEY = "jobName"; 34 | static final String JOB_INSTANCE_ID_KEY = "jobInstanceId"; 35 | protected static final String JOB_KEY_KEY = "jobKey"; 36 | protected static final String JOB_PARAMETERS_KEY = "jobParameters"; 37 | 38 | @PostConstruct 39 | public void init() { 40 | getCollection().ensureIndex(jobInstanceIdObj(1L)); 41 | } 42 | 43 | public JobInstance createJobInstance(String jobName, final JobParameters jobParameters) { 44 | Assert.notNull(jobName, "Job name must not be null."); 45 | Assert.notNull(jobParameters, "JobParameters must not be null."); 46 | 47 | Assert.state(getJobInstance(jobName, jobParameters) == null, 48 | "JobInstance must not already exist"); 49 | 50 | Long jobId = getNextId(JobInstance.class.getSimpleName()); 51 | 52 | JobInstance jobInstance = new JobInstance(jobId, jobParameters, jobName); 53 | 54 | jobInstance.incrementVersion(); 55 | 56 | Map jobParams = jobParameters.getParameters(); 57 | Map paramMap = new HashMap(jobParams.size()); 58 | for (Map.Entry entry : jobParams.entrySet()) { 59 | paramMap.put(entry.getKey().replaceAll(DOT_STRING, DOT_ESCAPE_STRING), entry.getValue().getValue()); 60 | } 61 | getCollection().save(start() 62 | .add(JOB_INSTANCE_ID_KEY, jobId) 63 | .add(JOB_NAME_KEY, jobName) 64 | .add(JOB_KEY_KEY, createJobKey(jobParameters)) 65 | .add(VERSION_KEY, jobInstance.getVersion()) 66 | .add(JOB_PARAMETERS_KEY, new BasicDBObject(paramMap)).get()); 67 | return jobInstance; 68 | } 69 | 70 | public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { 71 | Assert.notNull(jobName, "Job name must not be null."); 72 | Assert.notNull(jobParameters, "JobParameters must not be null."); 73 | 74 | String jobKey = createJobKey(jobParameters); 75 | 76 | return mapJobInstance(getCollection().findOne(start() 77 | .add(JOB_NAME_KEY, jobName) 78 | .add(JOB_KEY_KEY, jobKey).get()), jobParameters); 79 | } 80 | 81 | public JobInstance getJobInstance(Long instanceId) { 82 | return mapJobInstance(getCollection().findOne(jobInstanceIdObj(instanceId))); 83 | } 84 | 85 | public JobInstance getJobInstance(JobExecution jobExecution) { 86 | DBObject instanceId = db.getCollection(JobExecution.class.getSimpleName()).findOne(MongoJobExecutionDao.jobExecutionIdObj(jobExecution.getId()), jobInstanceIdObj(1L)); 87 | removeSystemFields(instanceId); 88 | return mapJobInstance(getCollection().findOne(instanceId)); 89 | } 90 | 91 | public List getJobInstances(String jobName, int start, int count) { 92 | return mapJobInstances(getCollection().find(new BasicDBObject(JOB_NAME_KEY, jobName)).sort(jobInstanceIdObj(-1L)).skip(start).limit(count)); 93 | } 94 | 95 | @SuppressWarnings({"unchecked"}) 96 | public List getJobNames() { 97 | List results = getCollection().distinct(JOB_NAME_KEY); 98 | Collections.sort(results); 99 | return results; 100 | } 101 | 102 | protected String createJobKey(JobParameters jobParameters) { 103 | 104 | Map props = jobParameters.getParameters(); 105 | StringBuilder stringBuilder = new StringBuilder(); 106 | List keys = new ArrayList(props.keySet()); 107 | Collections.sort(keys); 108 | for (String key : keys) { 109 | stringBuilder.append(key).append("=").append(props.get(key).toString()).append(";"); 110 | } 111 | 112 | MessageDigest digest; 113 | try { 114 | digest = MessageDigest.getInstance("MD5"); 115 | } catch (NoSuchAlgorithmException e) { 116 | throw new IllegalStateException( 117 | "MD5 algorithm not available. Fatal (should be in the JDK)."); 118 | } 119 | 120 | try { 121 | byte[] bytes = digest.digest(stringBuilder.toString().getBytes( 122 | "UTF-8")); 123 | return String.format("%032x", new BigInteger(1, bytes)); 124 | } catch (UnsupportedEncodingException e) { 125 | throw new IllegalStateException( 126 | "UTF-8 encoding not available. Fatal (should be in the JDK)."); 127 | } 128 | } 129 | 130 | @Override 131 | protected DBCollection getCollection() { 132 | return db.getCollection(JobInstance.class.getSimpleName()); 133 | } 134 | 135 | private List mapJobInstances(DBCursor dbCursor) { 136 | List results = new ArrayList(); 137 | while (dbCursor.hasNext()) { 138 | results.add(mapJobInstance(dbCursor.next())); 139 | } 140 | return results; 141 | } 142 | 143 | private JobInstance mapJobInstance(DBObject dbObject) { 144 | return mapJobInstance(dbObject, null); 145 | } 146 | 147 | private JobInstance mapJobInstance(DBObject dbObject, JobParameters jobParameters) { 148 | JobInstance jobInstance = null; 149 | if (dbObject != null) { 150 | Long id = (Long) dbObject.get(JOB_INSTANCE_ID_KEY); 151 | if (jobParameters == null) { 152 | jobParameters = getJobParameters(id); 153 | } 154 | jobInstance = new JobInstance(id, jobParameters, (String) dbObject.get(JOB_NAME_KEY)); // should always be at version=0 because they never get updated 155 | jobInstance.incrementVersion(); 156 | } 157 | return jobInstance; 158 | } 159 | 160 | @SuppressWarnings({"unchecked"}) 161 | private JobParameters getJobParameters(Long jobInstanceId) { 162 | final Map jobParamsMap = (Map) getCollection().findOne(new BasicDBObject(jobInstanceIdObj(jobInstanceId))).get(JOB_PARAMETERS_KEY); 163 | 164 | Map map = new HashMap(jobParamsMap.size()); 165 | for (Map.Entry entry : jobParamsMap.entrySet()) { 166 | Object param = entry.getValue(); 167 | String key = entry.getKey().replaceAll(DOT_ESCAPE_STRING, DOT_STRING); 168 | if (param instanceof String) { 169 | map.put(key, new JobParameter((String) param)); 170 | } else if (param instanceof Long) { 171 | map.put(key, new JobParameter((Long) param)); 172 | } else if (param instanceof Double) { 173 | map.put(key, new JobParameter((Double) param)); 174 | } else if (param instanceof Date) { 175 | map.put(key, new JobParameter((Date) param)); 176 | } else { 177 | map.put(key, null); 178 | } 179 | } 180 | return new JobParameters(map); 181 | } 182 | 183 | static BasicDBObject jobInstanceIdObj(Long id) { 184 | return new BasicDBObject(MongoJobInstanceDao.JOB_INSTANCE_ID_KEY, id); 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/batch/mongo/dao/MongoStepExecutionDao.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.*; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.batch.core.BatchStatus; 7 | import org.springframework.batch.core.ExitStatus; 8 | import org.springframework.batch.core.JobExecution; 9 | import org.springframework.batch.core.StepExecution; 10 | import org.springframework.batch.core.repository.dao.StepExecutionDao; 11 | import org.springframework.dao.OptimisticLockingFailureException; 12 | import org.springframework.stereotype.Repository; 13 | import org.springframework.util.Assert; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.util.Date; 17 | 18 | import static com.mongodb.BasicDBObjectBuilder.start; 19 | import static org.springframework.batch.mongo.dao.MongoJobExecutionDao.JOB_EXECUTION_ID_KEY; 20 | import static org.springframework.batch.mongo.dao.MongoJobExecutionDao.jobExecutionIdObj; 21 | import static org.springframework.util.Assert.notNull; 22 | 23 | /** 24 | * Created by IntelliJ IDEA. 25 | * 26 | * @author Baruch S. 27 | * @since Apr 15, 2010 28 | */ 29 | @Repository 30 | public class MongoStepExecutionDao extends AbstractMongoDao implements StepExecutionDao { 31 | protected static final String STEP_EXECUTION_ID_KEY = "stepExecutionId"; 32 | public static final String STEP_NAME_KEY = "stepName"; 33 | public static final String COMMIT_COUNT_KEY = "commitCount"; 34 | public static final String READ_COUNT_KEY = "readCount"; 35 | public static final String FILTER_COUT_KEY = "filterCout"; 36 | public static final String WRITE_COUNT_KEY = "writeCount"; 37 | protected static final String READ_SKIP_COUNT_KEY = "readSkipCount"; 38 | protected static final String WRITE_SKIP_COUNT_KEY = "writeSkipCount"; 39 | protected static final String PROCESS_SKIP_COUT_KEY = "processSkipCout"; 40 | protected static final String ROLLBACK_COUNT_KEY = "rollbackCount"; 41 | private static final Logger LOG = LoggerFactory.getLogger(MongoStepExecutionDao.class); 42 | 43 | @PostConstruct 44 | public void init() { 45 | getCollection().ensureIndex(BasicDBObjectBuilder.start().add(STEP_EXECUTION_ID_KEY, 1).add(JOB_EXECUTION_ID_KEY, 1).get()); 46 | 47 | } 48 | 49 | public void saveStepExecution(StepExecution stepExecution) { 50 | Assert.isNull(stepExecution.getId(), 51 | "to-be-saved (not updated) StepExecution can't already have an id assigned"); 52 | Assert.isNull(stepExecution.getVersion(), 53 | "to-be-saved (not updated) StepExecution can't already have a version assigned"); 54 | 55 | validateStepExecution(stepExecution); 56 | 57 | stepExecution.setId(getNextId(StepExecution.class.getSimpleName())); 58 | stepExecution.incrementVersion(); // should be 0 now 59 | DBObject object = toDbObjectWithoutVersion(stepExecution); 60 | object.put(VERSION_KEY, stepExecution.getVersion()); 61 | getCollection().save(object); 62 | 63 | } 64 | 65 | private DBObject toDbObjectWithoutVersion(StepExecution stepExecution) { 66 | return start() 67 | .add(STEP_EXECUTION_ID_KEY, stepExecution.getId()) 68 | .add(STEP_NAME_KEY, stepExecution.getStepName()) 69 | .add(JOB_EXECUTION_ID_KEY, stepExecution.getJobExecutionId()) 70 | .add(START_TIME_KEY, stepExecution.getStartTime()) 71 | .add(END_TIME_KEY, stepExecution.getEndTime()) 72 | .add(STATUS_KEY, stepExecution.getStatus().toString()) 73 | .add(COMMIT_COUNT_KEY, stepExecution.getCommitCount()) 74 | .add(READ_COUNT_KEY, stepExecution.getReadCount()) 75 | .add(FILTER_COUT_KEY, stepExecution.getFilterCount()) 76 | .add(WRITE_COUNT_KEY, stepExecution.getWriteCount()) 77 | .add(EXIT_CODE_KEY, stepExecution.getExitStatus().getExitCode()) 78 | .add(EXIT_MESSAGE_KEY, stepExecution.getExitStatus().getExitDescription()) 79 | .add(READ_SKIP_COUNT_KEY, stepExecution.getReadSkipCount()) 80 | .add(WRITE_SKIP_COUNT_KEY, stepExecution.getWriteSkipCount()) 81 | .add(PROCESS_SKIP_COUT_KEY, stepExecution.getProcessSkipCount()) 82 | .add(ROLLBACK_COUNT_KEY, stepExecution.getRollbackCount()) 83 | .add(LAST_UPDATED_KEY, stepExecution.getLastUpdated()).get(); 84 | } 85 | 86 | public synchronized void updateStepExecution(StepExecution stepExecution) { 87 | // Attempt to prevent concurrent modification errors by blocking here if 88 | // someone is already trying to do it. 89 | Integer currentVersion = stepExecution.getVersion(); 90 | Integer newVersion = currentVersion + 1; 91 | DBObject object = toDbObjectWithoutVersion(stepExecution); 92 | object.put(VERSION_KEY, newVersion); 93 | getCollection().update(start() 94 | .add(STEP_EXECUTION_ID_KEY, stepExecution.getId()) 95 | .add(VERSION_KEY, currentVersion).get(), 96 | object); 97 | 98 | // Avoid concurrent modifications... 99 | DBObject lastError = db.getLastError(); 100 | if (!((Boolean) lastError.get(UPDATED_EXISTING_STATUS))) { 101 | LOG.error("Update returned status {}", lastError); 102 | DBObject existingStepExecution = getCollection().findOne(stepExecutionIdObj(stepExecution.getId()), new BasicDBObject(VERSION_KEY, 1)); 103 | if (existingStepExecution == null) { 104 | throw new IllegalArgumentException("Can't update this stepExecution, it was never saved."); 105 | } 106 | Integer curentVersion = ((Integer) existingStepExecution.get(VERSION_KEY)); 107 | throw new OptimisticLockingFailureException("Attempt to update job execution id=" 108 | + stepExecution.getId() + " with wrong version (" + currentVersion 109 | + "), where current version is " + curentVersion); 110 | } 111 | 112 | stepExecution.incrementVersion(); 113 | } 114 | 115 | 116 | static BasicDBObject stepExecutionIdObj(Long id) { 117 | return new BasicDBObject(STEP_EXECUTION_ID_KEY, id); 118 | } 119 | 120 | 121 | public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) { 122 | return mapStepExecution(getCollection().findOne(BasicDBObjectBuilder.start() 123 | .add(STEP_EXECUTION_ID_KEY, stepExecutionId) 124 | .add(JOB_EXECUTION_ID_KEY, jobExecution.getId()).get()), jobExecution); 125 | } 126 | 127 | private StepExecution mapStepExecution(DBObject object, JobExecution jobExecution) { 128 | if (object == null) { 129 | return null; 130 | } 131 | StepExecution stepExecution = new StepExecution((String) object.get(STEP_NAME_KEY), jobExecution, ((Long) object.get(STEP_EXECUTION_ID_KEY))); 132 | stepExecution.setStartTime((Date) object.get(START_TIME_KEY)); 133 | stepExecution.setEndTime((Date) object.get(END_TIME_KEY)); 134 | stepExecution.setStatus(BatchStatus.valueOf((String) object.get(STATUS_KEY))); 135 | stepExecution.setCommitCount((Integer) object.get(COMMIT_COUNT_KEY)); 136 | stepExecution.setReadCount((Integer) object.get(READ_COUNT_KEY)); 137 | stepExecution.setFilterCount((Integer) object.get(FILTER_COUT_KEY)); 138 | stepExecution.setWriteCount((Integer) object.get(WRITE_COUNT_KEY)); 139 | stepExecution.setExitStatus(new ExitStatus((String) object.get(EXIT_CODE_KEY), ((String) object.get(EXIT_MESSAGE_KEY)))); 140 | stepExecution.setReadSkipCount((Integer) object.get(READ_SKIP_COUNT_KEY)); 141 | stepExecution.setWriteSkipCount((Integer) object.get(WRITE_SKIP_COUNT_KEY)); 142 | stepExecution.setProcessSkipCount((Integer) object.get(PROCESS_SKIP_COUT_KEY)); 143 | stepExecution.setRollbackCount((Integer) object.get(ROLLBACK_COUNT_KEY)); 144 | stepExecution.setLastUpdated((Date) object.get(LAST_UPDATED_KEY)); 145 | stepExecution.setVersion((Integer) object.get(VERSION_KEY)); 146 | return stepExecution; 147 | 148 | } 149 | 150 | public void addStepExecutions(JobExecution jobExecution) { 151 | DBCursor stepsCoursor = getCollection().find(jobExecutionIdObj(jobExecution.getId())).sort(stepExecutionIdObj(1L)); 152 | while (stepsCoursor.hasNext()) { 153 | DBObject stepObject = stepsCoursor.next(); 154 | //Calls constructor of StepExecution, which adds the step; Wow, that's unclear code! 155 | mapStepExecution(stepObject, jobExecution); 156 | } 157 | } 158 | 159 | @Override 160 | protected DBCollection getCollection() { 161 | return db.getCollection(StepExecution.class.getSimpleName()); 162 | } 163 | 164 | private void validateStepExecution(StepExecution stepExecution) { 165 | notNull(stepExecution); 166 | notNull(stepExecution.getStepName(), "StepExecution step name cannot be null."); 167 | notNull(stepExecution.getStartTime(), "StepExecution start time cannot be null."); 168 | notNull(stepExecution.getStatus(), "StepExecution status cannot be null."); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/javaconfig/util/ConfigurationSupport.java: -------------------------------------------------------------------------------- 1 | package org.springframework.javaconfig.util; 2 | 3 | import org.springframework.beans.factory.FactoryBean; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 6 | import org.springframework.context.*; 7 | import org.springframework.stereotype.Component; 8 | 9 | 10 | /** 11 | * Created by IntelliJ IDEA. 12 | * 13 | * @author Baruch S. 14 | * @since Apr 14, 2010 15 | */ 16 | @Component 17 | public class ConfigurationSupport { 18 | 19 | @Autowired 20 | private AutowireCapableBeanFactory autowireCapableBeanFactory; 21 | 22 | @Autowired 23 | private ApplicationContext applicationContext; 24 | 25 | /** 26 | * Return the object created by this FactoryBean instance, first invoking 27 | * any container callbacks on the instance 28 | * 29 | * @param fb FactoryBean instance 30 | * @return the object created by the configured FactoryBean instance 31 | */ 32 | @SuppressWarnings({"unchecked"}) 33 | public T getObject(FactoryBean fb) { 34 | try { 35 | 36 | FactoryBean factoryBean = (FactoryBean) getConfigured(fb); 37 | return (T) factoryBean.getObject(); 38 | } 39 | catch (Exception ex) { 40 | // TODO clean up 41 | throw new RuntimeException(ex); 42 | } 43 | } 44 | 45 | /* Invoke callbacks on the object, as though it was configured in the 46 | * factory 47 | * @param o object to configure 48 | * @return object after callbacks have been called on it 49 | */ 50 | 51 | private Object getConfigured(Object o) { 52 | if (this.autowireCapableBeanFactory == null) { 53 | throw new UnsupportedOperationException( 54 | "Cannot configure object - not running in an AutowireCapableBeanFactory"); 55 | } 56 | 57 | autowireCapableBeanFactory.initializeBean(o, null); 58 | 59 | // TODO could replace with ApplicationContextAwareProcessor call if that class were public 60 | if (this.applicationContext != null) { 61 | if (o instanceof ResourceLoaderAware) { 62 | ((ResourceLoaderAware) o).setResourceLoader(this.applicationContext); 63 | } 64 | if (o instanceof ApplicationEventPublisherAware) { 65 | ((ApplicationEventPublisherAware) o).setApplicationEventPublisher(this.applicationContext); 66 | } 67 | if (o instanceof MessageSourceAware) { 68 | ((MessageSourceAware) o).setMessageSource(this.applicationContext); 69 | } 70 | if (o instanceof ApplicationContextAware) { 71 | ((ApplicationContextAware) o).setApplicationContext(this.applicationContext); 72 | } 73 | } 74 | 75 | return o; 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/resources/application-config.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/batch.properties: -------------------------------------------------------------------------------- 1 | app.db.name=app 2 | batch.db.name=batch 3 | batch.start.limit=100 4 | batch.commit.interval=1 5 | step.thread.max.pool.size=10 6 | step.thread.core.pool.size=2 -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | INFO 8 | 9 | 10 | 11 | 12 | 13 | springbatch-over-mongodb.log 14 | 15 | logFile.%d{yyyy-MM-dd}.log 16 | 17 | 18 | 19 | %-4relative [%thread] %-5level %logger{35} - %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/config/ApplicationTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.config; 2 | 3 | import org.springframework.batch.test.JobLauncherTestUtils; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * Created by IntelliJ IDEA. 9 | * 10 | * @author JBaruch 11 | * @since 19-Apr-2010 12 | */ 13 | @Configuration 14 | public class ApplicationTestConfiguration { 15 | 16 | @Bean 17 | public JobLauncherTestUtils jobLauncherTestUtils() { 18 | return new JobLauncherTestUtils(); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/config/JobConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.config; 2 | 3 | import org.springframework.batch.core.JobParametersIncrementer; 4 | import org.springframework.batch.core.Step; 5 | import org.springframework.batch.core.job.AbstractJob; 6 | import org.springframework.batch.core.job.SimpleJob; 7 | import org.springframework.batch.core.repository.JobRepository; 8 | import org.springframework.batch.core.step.item.SimpleStepFactoryBean; 9 | import org.springframework.batch.item.ItemReader; 10 | import org.springframework.batch.item.ItemWriter; 11 | import org.springframework.batch.support.transaction.ResourcelessTransactionManager; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.core.task.TaskExecutor; 17 | import org.springframework.javaconfig.util.ConfigurationSupport; 18 | 19 | import java.util.Arrays; 20 | 21 | /** 22 | * Created by IntelliJ IDEA. 23 | * 24 | * @author JBaruch 25 | * @since 19-Apr-2010 26 | */ 27 | @Configuration 28 | public class JobConfiguration { 29 | 30 | @Value("${batch.start.limit}") 31 | private int startLimit; 32 | 33 | @Value("${batch.commit.interval}") 34 | private int commitInterval; 35 | 36 | 37 | @Autowired 38 | private JobRepository jobRepository; 39 | 40 | @Autowired 41 | private ItemReader itemReader; 42 | 43 | @Autowired 44 | private ItemWriter itemWriter; 45 | 46 | @Autowired 47 | private TaskExecutor taskExecutor; 48 | 49 | @Autowired 50 | private ConfigurationSupport configurationSupport; 51 | 52 | @Autowired 53 | private JobParametersIncrementer jobParametersIncrementer; 54 | 55 | @Bean 56 | public Step step() { 57 | SimpleStepFactoryBean stepFactoryBean = new SimpleStepFactoryBean(); 58 | stepFactoryBean.setTransactionManager(new ResourcelessTransactionManager()); 59 | stepFactoryBean.setJobRepository(jobRepository); 60 | stepFactoryBean.setStartLimit(startLimit); 61 | stepFactoryBean.setCommitInterval(commitInterval); 62 | stepFactoryBean.setItemReader(itemReader); 63 | stepFactoryBean.setItemWriter(itemWriter); 64 | stepFactoryBean.setTaskExecutor(taskExecutor); 65 | return configurationSupport.getObject(stepFactoryBean); 66 | } 67 | 68 | 69 | @Bean 70 | public AbstractJob job() { 71 | SimpleJob job = new SimpleJob(); 72 | job.setJobParametersIncrementer(jobParametersIncrementer); 73 | job.setJobRepository(jobRepository); 74 | job.setSteps(Arrays.asList(step())); 75 | return job; 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/AbstractExecutionContextDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.DB; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.springframework.batch.core.JobExecution; 7 | import org.springframework.batch.core.JobInstance; 8 | import org.springframework.batch.core.JobParameters; 9 | import org.springframework.batch.core.StepExecution; 10 | import org.springframework.batch.core.repository.dao.ExecutionContextDao; 11 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 12 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 13 | import org.springframework.batch.core.repository.dao.StepExecutionDao; 14 | import org.springframework.batch.item.ExecutionContext; 15 | import org.springframework.batch.mongo.config.Database; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import java.util.Collections; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | 23 | /** 24 | * Tests for {@link ExecutionContextDao} implementations. 25 | */ 26 | @SuppressWarnings({"FieldCanBeLocal"}) 27 | public abstract class AbstractExecutionContextDaoTests { 28 | 29 | private JobInstanceDao jobInstanceDao; 30 | 31 | private JobExecutionDao jobExecutionDao; 32 | 33 | private StepExecutionDao stepExecutionDao; 34 | 35 | private ExecutionContextDao contextDao; 36 | 37 | private JobExecution jobExecution; 38 | 39 | private StepExecution stepExecution; 40 | 41 | @Autowired 42 | @Database(Database.Purpose.BATCH) 43 | protected DB db; 44 | 45 | @Before 46 | public void setUp() { 47 | db.dropDatabase(); 48 | jobInstanceDao = getJobInstanceDao(); 49 | jobExecutionDao = getJobExecutionDao(); 50 | stepExecutionDao = getStepExecutionDao(); 51 | contextDao = getExecutionContextDao(); 52 | 53 | JobInstance ji = jobInstanceDao.createJobInstance("testJob", new JobParameters()); 54 | jobExecution = new JobExecution(ji); 55 | jobExecutionDao.saveJobExecution(jobExecution); 56 | stepExecution = new StepExecution("stepName", jobExecution); 57 | stepExecutionDao.saveStepExecution(stepExecution); 58 | 59 | 60 | } 61 | 62 | /** 63 | * @return Configured {@link ExecutionContextDao} implementation ready for 64 | * use. 65 | */ 66 | protected abstract JobExecutionDao getJobExecutionDao(); 67 | 68 | /** 69 | * @return Configured {@link ExecutionContextDao} implementation ready for 70 | * use. 71 | */ 72 | protected abstract JobInstanceDao getJobInstanceDao(); 73 | 74 | /** 75 | * @return Configured {@link ExecutionContextDao} implementation ready for 76 | * use. 77 | */ 78 | protected abstract StepExecutionDao getStepExecutionDao(); 79 | 80 | /** 81 | * @return Configured {@link ExecutionContextDao} implementation ready for 82 | * use. 83 | */ 84 | protected abstract ExecutionContextDao getExecutionContextDao(); 85 | 86 | @Transactional 87 | @Test 88 | public void testSaveAndFindJobContext() { 89 | 90 | ExecutionContext ctx = new ExecutionContext(Collections.singletonMap("key", "value")); 91 | jobExecution.setExecutionContext(ctx); 92 | contextDao.saveExecutionContext(jobExecution); 93 | 94 | ExecutionContext retrieved = contextDao.getExecutionContext(jobExecution); 95 | assertEquals(ctx, retrieved); 96 | } 97 | 98 | @Transactional 99 | @Test 100 | public void testSaveAndFindEmptyJobContext() { 101 | 102 | ExecutionContext ctx = new ExecutionContext(); 103 | jobExecution.setExecutionContext(ctx); 104 | contextDao.saveExecutionContext(jobExecution); 105 | 106 | ExecutionContext retrieved = contextDao.getExecutionContext(jobExecution); 107 | assertEquals(ctx, retrieved); 108 | } 109 | 110 | @Transactional 111 | @Test 112 | public void testUpdateContext() { 113 | 114 | ExecutionContext ctx = new ExecutionContext(Collections 115 | .singletonMap("key", "value")); 116 | jobExecution.setExecutionContext(ctx); 117 | contextDao.saveExecutionContext(jobExecution); 118 | 119 | ctx.putLong("longKey", 7); 120 | contextDao.updateExecutionContext(jobExecution); 121 | 122 | ExecutionContext retrieved = contextDao.getExecutionContext(jobExecution); 123 | assertEquals(ctx, retrieved); 124 | assertEquals(7, retrieved.getLong("longKey")); 125 | } 126 | 127 | @Transactional 128 | @Test 129 | public void testSaveAndFindStepContext() { 130 | 131 | ExecutionContext ctx = new ExecutionContext(Collections.singletonMap("key", "value")); 132 | stepExecution.setExecutionContext(ctx); 133 | contextDao.saveExecutionContext(stepExecution); 134 | 135 | ExecutionContext retrieved = contextDao.getExecutionContext(stepExecution); 136 | assertEquals(ctx, retrieved); 137 | } 138 | 139 | @Transactional 140 | @Test 141 | public void testSaveAndFindEmptyStepContext() { 142 | 143 | ExecutionContext ctx = new ExecutionContext(); 144 | stepExecution.setExecutionContext(ctx); 145 | contextDao.saveExecutionContext(stepExecution); 146 | 147 | ExecutionContext retrieved = contextDao.getExecutionContext(stepExecution); 148 | assertEquals(ctx, retrieved); 149 | } 150 | 151 | @Transactional 152 | @Test 153 | public void testUpdateStepContext() { 154 | 155 | ExecutionContext ctx = new ExecutionContext(Collections.singletonMap("key", "value")); 156 | stepExecution.setExecutionContext(ctx); 157 | contextDao.saveExecutionContext(stepExecution); 158 | 159 | ctx.putLong("longKey", 7); 160 | contextDao.updateExecutionContext(stepExecution); 161 | 162 | ExecutionContext retrieved = contextDao.getExecutionContext(stepExecution); 163 | assertEquals(ctx, retrieved); 164 | assertEquals(7, retrieved.getLong("longKey")); 165 | } 166 | 167 | @Transactional 168 | @Test 169 | public void testStoreInteger() { 170 | 171 | ExecutionContext ec = new ExecutionContext(); 172 | ec.put("intValue", 343232); 173 | stepExecution.setExecutionContext(ec); 174 | contextDao.saveExecutionContext(stepExecution); 175 | ExecutionContext restoredEc = contextDao.getExecutionContext(stepExecution); 176 | assertEquals(ec, restoredEc); 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/AbstractJobDaoTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006-2007 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.batch.mongo.dao; 18 | 19 | import com.mongodb.BasicDBObject; 20 | import com.mongodb.DB; 21 | import com.mongodb.DBObject; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.springframework.batch.core.*; 25 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 26 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 27 | import org.springframework.batch.core.repository.dao.NoSuchObjectException; 28 | import org.springframework.batch.mongo.config.Database; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.transaction.annotation.Transactional; 31 | 32 | import java.util.Date; 33 | import java.util.List; 34 | 35 | import static org.junit.Assert.*; 36 | import static org.springframework.batch.mongo.dao.AbstractMongoDao.VERSION_KEY; 37 | import static org.springframework.batch.mongo.dao.MongoJobExecutionDao.JOB_EXECUTION_ID_KEY; 38 | import static org.springframework.batch.mongo.dao.MongoJobInstanceDao.JOB_INSTANCE_ID_KEY; 39 | import static org.springframework.batch.mongo.dao.MongoJobInstanceDao.JOB_NAME_KEY; 40 | 41 | /** 42 | * @author Dave Syer 43 | */ 44 | public abstract class AbstractJobDaoTests { 45 | 46 | protected JobInstanceDao jobInstanceDao; 47 | 48 | protected JobExecutionDao jobExecutionDao; 49 | 50 | protected JobParameters jobParameters = new JobParametersBuilder().addString("job.key", "jobKey").addLong("long", 51 | (long) 1).addDate("date", new Date(7)).addDouble("double", 7.7).toJobParameters(); 52 | 53 | protected JobInstance jobInstance; 54 | 55 | protected String jobName = "Job1"; 56 | 57 | protected JobExecution jobExecution; 58 | 59 | protected Date jobExecutionStartTime = new Date(System.currentTimeMillis()); 60 | 61 | @Autowired 62 | @Database(Database.Purpose.BATCH) 63 | protected DB db; 64 | 65 | /* 66 | * Because AbstractTransactionalSpringContextTests is used, this method will 67 | * be called by Spring to set the JobRepository. 68 | */ 69 | 70 | @Autowired 71 | public void setJobInstanceDao(JobInstanceDao jobInstanceDao) { 72 | this.jobInstanceDao = jobInstanceDao; 73 | } 74 | 75 | @Autowired 76 | public void setJobExecutionDao(JobExecutionDao jobExecutionDao) { 77 | this.jobExecutionDao = jobExecutionDao; 78 | } 79 | 80 | @Before 81 | public void onSetUpInTransaction() throws Exception { 82 | db.dropDatabase(); 83 | // Create job. 84 | jobInstance = jobInstanceDao.createJobInstance(jobName, jobParameters); 85 | 86 | // Create an execution 87 | jobExecutionStartTime = new Date(System.currentTimeMillis()); 88 | jobExecution = new JobExecution(jobInstance); 89 | jobExecution.setStartTime(jobExecutionStartTime); 90 | jobExecution.setStatus(BatchStatus.STARTED); 91 | jobExecutionDao.saveJobExecution(jobExecution); 92 | } 93 | 94 | @Transactional 95 | @Test 96 | public void testVersionIsNotNullForJob() throws Exception { 97 | assertEquals(0, getVersion(JobInstance.class.getSimpleName(), jobInstance.getId(), JOB_INSTANCE_ID_KEY)); 98 | } 99 | 100 | private int getVersion(String collectionName, Long id, String idKey) { 101 | DBObject dbObject = db.getCollection(collectionName).findOne(new BasicDBObject(idKey, id), new BasicDBObject(VERSION_KEY, 1)); 102 | return dbObject == null ? 0 : (Integer) dbObject.get(VERSION_KEY); 103 | } 104 | 105 | @Transactional 106 | @Test 107 | public void testVersionIsNotNullForJobExecution() throws Exception { 108 | assertEquals(0, getVersion(JobExecution.class.getSimpleName(), jobExecution.getId(), JOB_EXECUTION_ID_KEY)); 109 | } 110 | 111 | @Transactional 112 | @Test 113 | public void testFindNonExistentJob() { 114 | // No job should be found since it hasn't been created. 115 | JobInstance jobInstance = jobInstanceDao.getJobInstance("nonexistentJob", jobParameters); 116 | assertNull(jobInstance); 117 | } 118 | 119 | @Transactional 120 | @Test 121 | public void testFindJob() { 122 | 123 | JobInstance instance = jobInstanceDao.getJobInstance(jobName, jobParameters); 124 | assertNotNull(instance); 125 | assertTrue(jobInstance.equals(instance)); 126 | assertEquals(jobParameters, instance.getJobParameters()); 127 | } 128 | 129 | @Transactional 130 | @Test(expected = IllegalArgumentException.class) 131 | public void testFindJobWithNullRuntime() { 132 | jobInstanceDao.getJobInstance(null, null); 133 | } 134 | 135 | /** 136 | * Test that ensures that if you create a job with a given name, then find a 137 | * job with the same name, but other pieces of the identifier different, you 138 | * get no result, not the existing one. 139 | */ 140 | @Transactional 141 | @Test 142 | public void testCreateJobWithExistingName() { 143 | 144 | String scheduledJob = "ScheduledJob"; 145 | jobInstanceDao.createJobInstance(scheduledJob, jobParameters); 146 | 147 | // Modifying the key should bring back a completely different 148 | // JobInstance 149 | JobParameters tempProps = new JobParametersBuilder().addString("job.key", "testKey1").toJobParameters(); 150 | 151 | JobInstance instance; 152 | instance = jobInstanceDao.getJobInstance(scheduledJob, jobParameters); 153 | assertNotNull(instance); 154 | assertEquals(jobParameters, instance.getJobParameters()); 155 | 156 | instance = jobInstanceDao.getJobInstance(scheduledJob, tempProps); 157 | assertNull(instance); 158 | 159 | } 160 | 161 | @Transactional 162 | @Test 163 | public void testUpdateJobExecution() { 164 | 165 | jobExecution.setStatus(BatchStatus.COMPLETED); 166 | jobExecution.setExitStatus(ExitStatus.COMPLETED); 167 | jobExecution.setEndTime(new Date(System.currentTimeMillis())); 168 | jobExecutionDao.updateJobExecution(jobExecution); 169 | 170 | List executions = jobExecutionDao.findJobExecutions(jobInstance); 171 | assertEquals(executions.size(), 1); 172 | validateJobExecution(jobExecution, executions.get(0)); 173 | 174 | } 175 | 176 | @Transactional 177 | @Test 178 | public void testSaveJobExecution() { 179 | 180 | List executions = jobExecutionDao.findJobExecutions(jobInstance); 181 | assertEquals(executions.size(), 1); 182 | validateJobExecution(jobExecution, executions.get(0)); 183 | } 184 | 185 | @Transactional 186 | @Test(expected = NoSuchObjectException.class) 187 | public void testUpdateInvalidJobExecution() { 188 | // id is invalid 189 | JobExecution execution = new JobExecution(jobInstance, (long) 29432); 190 | execution.incrementVersion(); 191 | jobExecutionDao.updateJobExecution(execution); 192 | } 193 | 194 | @Transactional 195 | @Test(expected = IllegalArgumentException.class) 196 | public void testUpdateNullIdJobExection() { 197 | JobExecution execution = new JobExecution(jobInstance); 198 | jobExecutionDao.updateJobExecution(execution); 199 | } 200 | 201 | 202 | @Transactional 203 | @Test 204 | public void testJobWithSimpleJobIdentifier() throws Exception { 205 | 206 | String testJob = "test"; 207 | // Create job. 208 | jobInstance = jobInstanceDao.createJobInstance(testJob, jobParameters); 209 | DBObject dbObject = db.getCollection(JobInstance.class.getSimpleName()).findOne(new BasicDBObject(JOB_INSTANCE_ID_KEY, jobInstance.getId())); 210 | assertEquals("test", dbObject.get(JOB_NAME_KEY)); 211 | } 212 | 213 | @Transactional 214 | @Test 215 | public void testJobWithDefaultJobIdentifier() throws Exception { 216 | 217 | String testDefaultJob = "testDefault"; 218 | // Create job. 219 | jobInstance = jobInstanceDao.createJobInstance(testDefaultJob, jobParameters); 220 | 221 | JobInstance instance = jobInstanceDao.getJobInstance(testDefaultJob, jobParameters); 222 | 223 | assertNotNull(instance); 224 | assertEquals(jobParameters.getString("job.key"), instance.getJobParameters().getString( 225 | "job.key")); 226 | 227 | } 228 | 229 | @Transactional 230 | @Test 231 | public void testFindJobExecutions() { 232 | 233 | List results = jobExecutionDao.findJobExecutions(jobInstance); 234 | assertEquals(results.size(), 1); 235 | validateJobExecution(jobExecution, results.get(0)); 236 | } 237 | 238 | @Transactional 239 | @Test 240 | public void testFindJobsWithProperties() throws Exception { 241 | 242 | } 243 | 244 | private void validateJobExecution(JobExecution lhs, JobExecution rhs) { 245 | 246 | // equals operator only checks id 247 | assertEquals(lhs, rhs); 248 | assertEquals(lhs.getStartTime(), rhs.getStartTime()); 249 | assertEquals(lhs.getEndTime(), rhs.getEndTime()); 250 | assertEquals(lhs.getStatus(), rhs.getStatus()); 251 | assertEquals(lhs.getExitStatus(), rhs.getExitStatus()); 252 | } 253 | 254 | @Transactional 255 | @Test 256 | public void testGetLastJobExecution() { 257 | JobExecution lastExecution = new JobExecution(jobInstance); 258 | lastExecution.setStatus(BatchStatus.STARTED); 259 | 260 | int JUMP_INTO_FUTURE = 1000; // makes sure start time is 'greatest' 261 | lastExecution.setCreateTime(new Date(System.currentTimeMillis() + JUMP_INTO_FUTURE)); 262 | jobExecutionDao.saveJobExecution(lastExecution); 263 | 264 | assertEquals(lastExecution, jobExecutionDao.getLastJobExecution(jobInstance)); 265 | } 266 | 267 | /** 268 | * Trying to create instance twice for the same job+parameters causes error 269 | */ 270 | @Transactional 271 | @Test(expected = IllegalStateException.class) 272 | public void testCreateDuplicateInstance() { 273 | jobParameters = new JobParameters(); 274 | jobInstanceDao.createJobInstance(jobName, jobParameters); 275 | jobInstanceDao.createJobInstance(jobName, jobParameters); 276 | } 277 | 278 | @Transactional 279 | @Test 280 | public void testCreationAddsVersion() { 281 | jobInstance = jobInstanceDao.createJobInstance("testCreationAddsVersion", new JobParameters()); 282 | assertNotNull(jobInstance.getVersion()); 283 | } 284 | 285 | @Transactional 286 | @Test 287 | public void testSaveAddsVersionAndId() { 288 | 289 | JobExecution jobExecution = new JobExecution(jobInstance); 290 | 291 | assertNull(jobExecution.getId()); 292 | assertNull(jobExecution.getVersion()); 293 | 294 | jobExecutionDao.saveJobExecution(jobExecution); 295 | 296 | assertNotNull(jobExecution.getId()); 297 | assertNotNull(jobExecution.getVersion()); 298 | } 299 | 300 | @Transactional 301 | @Test 302 | public void testUpdateIncrementsVersion() { 303 | int version = jobExecution.getVersion(); 304 | 305 | jobExecutionDao.updateJobExecution(jobExecution); 306 | 307 | assertEquals(version + 1, jobExecution.getVersion().intValue()); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/AbstractJobExecutionDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.DB; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.springframework.batch.core.*; 7 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 8 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 9 | import org.springframework.batch.core.repository.dao.StepExecutionDao; 10 | import org.springframework.batch.mongo.config.Database; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.dao.OptimisticLockingFailureException; 13 | import org.springframework.transaction.annotation.Transactional; 14 | import org.springframework.util.Assert; 15 | 16 | import java.util.*; 17 | 18 | import static org.junit.Assert.*; 19 | 20 | public abstract class AbstractJobExecutionDaoTests { 21 | 22 | protected JobExecutionDao dao; 23 | 24 | protected JobInstance jobInstance; 25 | 26 | protected JobExecution execution; 27 | 28 | @Autowired 29 | @Database(Database.Purpose.BATCH) 30 | protected DB db; 31 | 32 | /** 33 | * @return tested object ready for use 34 | */ 35 | protected abstract JobExecutionDao getJobExecutionDao(); 36 | 37 | protected abstract JobInstanceDao getJobInstanceDao(); 38 | 39 | /** 40 | * @return tested object ready for use 41 | */ 42 | protected StepExecutionDao getStepExecutionDao() { 43 | return null; 44 | } 45 | 46 | @Before 47 | public void onSetUp() throws Exception { 48 | db.dropDatabase(); 49 | dao = getJobExecutionDao(); 50 | jobInstance = getJobInstanceDao().createJobInstance("execTestJob", new JobParameters()); 51 | execution = new JobExecution(jobInstance); 52 | } 53 | 54 | /** 55 | * Save and find a job execution. 56 | */ 57 | @Transactional 58 | @Test 59 | public void testSaveAndFind() { 60 | 61 | execution.setStartTime(new Date(System.currentTimeMillis())); 62 | execution.setLastUpdated(new Date(System.currentTimeMillis())); 63 | execution.setExitStatus(ExitStatus.UNKNOWN); 64 | execution.setEndTime(new Date(System.currentTimeMillis())); 65 | dao.saveJobExecution(execution); 66 | 67 | List executions = dao.findJobExecutions(jobInstance); 68 | assertEquals(1, executions.size()); 69 | assertEquals(execution, executions.get(0)); 70 | assertExecutionsAreEqual(execution, executions.get(0)); 71 | } 72 | 73 | /** 74 | * Executions should be returned in the reverse order they were saved. 75 | */ 76 | @Transactional 77 | @Test 78 | public void testFindExecutionsOrdering() { 79 | 80 | List execs = new ArrayList(); 81 | 82 | for (int i = 0; i < 10; i++) { 83 | JobExecution exec = new JobExecution(jobInstance); 84 | exec.setCreateTime(new Date(i)); 85 | execs.add(exec); 86 | dao.saveJobExecution(exec); 87 | } 88 | 89 | List retrieved = dao.findJobExecutions(jobInstance); 90 | Collections.reverse(retrieved); 91 | 92 | for (int i = 0; i < 10; i++) { 93 | assertExecutionsAreEqual(execs.get(i), retrieved.get(i)); 94 | } 95 | 96 | } 97 | 98 | /** 99 | * Save and find a job execution. 100 | */ 101 | @Transactional 102 | @Test 103 | public void testFindNonExistentExecutions() { 104 | List executions = dao.findJobExecutions(jobInstance); 105 | assertEquals(0, executions.size()); 106 | } 107 | 108 | /** 109 | * Saving sets id to the entity. 110 | */ 111 | @Transactional 112 | @Test 113 | public void testSaveAddsIdAndVersion() { 114 | 115 | assertNull(execution.getId()); 116 | assertNull(execution.getVersion()); 117 | dao.saveJobExecution(execution); 118 | assertNotNull(execution.getId()); 119 | assertNotNull(execution.getVersion()); 120 | } 121 | 122 | /** 123 | * Update and retrieve job execution - check attributes have changed as 124 | * expected. 125 | */ 126 | @Transactional 127 | @Test 128 | public void testUpdateExecution() { 129 | execution.setStatus(BatchStatus.STARTED); 130 | dao.saveJobExecution(execution); 131 | 132 | execution.setLastUpdated(new Date(0)); 133 | execution.setStatus(BatchStatus.COMPLETED); 134 | dao.updateJobExecution(execution); 135 | 136 | JobExecution updated = dao.findJobExecutions(jobInstance).get(0); 137 | assertEquals(execution, updated); 138 | assertEquals(BatchStatus.COMPLETED, updated.getStatus()); 139 | assertExecutionsAreEqual(execution, updated); 140 | } 141 | 142 | /** 143 | * Check the execution with most recent start time is returned 144 | */ 145 | @Transactional 146 | @Test 147 | public void testGetLastExecution() { 148 | JobExecution exec1 = new JobExecution(jobInstance); 149 | exec1.setCreateTime(new Date(0)); 150 | 151 | JobExecution exec2 = new JobExecution(jobInstance); 152 | exec2.setCreateTime(new Date(1)); 153 | 154 | dao.saveJobExecution(exec1); 155 | dao.saveJobExecution(exec2); 156 | 157 | JobExecution last = dao.getLastJobExecution(jobInstance); 158 | assertEquals(exec2, last); 159 | } 160 | 161 | /** 162 | * Check the execution is returned 163 | */ 164 | @Transactional 165 | @Test 166 | public void testGetMissingLastExecution() { 167 | JobExecution value = dao.getLastJobExecution(jobInstance); 168 | assertNull(value); 169 | } 170 | 171 | /** 172 | * Check the execution is returned 173 | */ 174 | @Transactional 175 | @Test 176 | public void testFindRunningExecutions() { 177 | 178 | JobExecution exec = new JobExecution(jobInstance); 179 | exec.setCreateTime(new Date(0)); 180 | exec.setEndTime(new Date(1L)); 181 | exec.setLastUpdated(new Date(5L)); 182 | dao.saveJobExecution(exec); 183 | 184 | exec = new JobExecution(jobInstance); 185 | exec.setLastUpdated(new Date(5L)); 186 | exec.createStepExecution("step"); 187 | dao.saveJobExecution(exec); 188 | 189 | StepExecutionDao stepExecutionDao = getStepExecutionDao(); 190 | if (stepExecutionDao != null) { 191 | for (StepExecution stepExecution : exec.getStepExecutions()) { 192 | stepExecutionDao.saveStepExecution(stepExecution); 193 | } 194 | } 195 | 196 | Set values = dao.findRunningJobExecutions(exec.getJobInstance().getJobName()); 197 | 198 | assertEquals(1, values.size()); 199 | JobExecution value = values.iterator().next(); 200 | assertEquals(exec, value); 201 | assertEquals(5L, value.getLastUpdated().getTime()); 202 | 203 | } 204 | 205 | /** 206 | * Check the execution is returned 207 | */ 208 | @Transactional 209 | @Test 210 | public void testNoRunningExecutions() { 211 | Set values = dao.findRunningJobExecutions("no-such-job"); 212 | assertEquals(0, values.size()); 213 | } 214 | 215 | /** 216 | * Check the execution is returned 217 | */ 218 | @Transactional 219 | @Test 220 | public void testGetExecution() { 221 | JobExecution exec = new JobExecution(jobInstance); 222 | exec.setCreateTime(new Date(0)); 223 | exec.createStepExecution("step"); 224 | 225 | dao.saveJobExecution(exec); 226 | StepExecutionDao stepExecutionDao = getStepExecutionDao(); 227 | if (stepExecutionDao != null) { 228 | for (StepExecution stepExecution : exec.getStepExecutions()) { 229 | stepExecutionDao.saveStepExecution(stepExecution); 230 | } 231 | } 232 | JobExecution value = dao.getJobExecution(exec.getId()); 233 | 234 | assertEquals(exec, value); 235 | // N.B. the job instance is not re-hydrated in the JDBC case... 236 | } 237 | 238 | /** 239 | * Check the execution is returned 240 | */ 241 | @Transactional 242 | @Test 243 | public void testGetMissingExecution() { 244 | JobExecution value = dao.getJobExecution(54321L); 245 | assertNull(value); 246 | } 247 | 248 | /** 249 | * Exception should be raised when the version of update argument doesn't 250 | * match the version of persisted entity. 251 | */ 252 | @Transactional 253 | @Test(expected = OptimisticLockingFailureException.class) 254 | public void testConcurrentModificationException() { 255 | 256 | JobExecution exec1 = new JobExecution(jobInstance); 257 | dao.saveJobExecution(exec1); 258 | 259 | JobExecution exec2 = new JobExecution(jobInstance); 260 | exec2.setId(exec1.getId()); 261 | 262 | exec2.incrementVersion(); 263 | assertEquals((Integer) 0, exec1.getVersion()); 264 | assertEquals(exec1.getVersion(), exec2.getVersion()); 265 | 266 | dao.updateJobExecution(exec1); 267 | assertEquals((Integer) 1, exec1.getVersion()); 268 | dao.updateJobExecution(exec2); 269 | } 270 | 271 | /** 272 | * Successful synchronization from STARTED to STOPPING status. 273 | */ 274 | @Transactional 275 | @Test 276 | public void testSynchronizeStatusUpgrade() { 277 | 278 | JobExecution exec1 = new JobExecution(jobInstance); 279 | exec1.setStatus(BatchStatus.STOPPING); 280 | dao.saveJobExecution(exec1); 281 | 282 | JobExecution exec2 = new JobExecution(jobInstance); 283 | Assert.state(exec1.getId() != null); 284 | exec2.setId(exec1.getId()); 285 | 286 | exec2.setStatus(BatchStatus.STARTED); 287 | exec2.setVersion(7); 288 | Assert.state(!exec1.getVersion().equals(exec2.getVersion())); 289 | Assert.state(exec1.getStatus() != exec2.getStatus()); 290 | 291 | dao.synchronizeStatus(exec2); 292 | 293 | assertEquals(exec1.getVersion(), exec2.getVersion()); 294 | assertEquals(exec1.getStatus(), exec2.getStatus()); 295 | } 296 | 297 | /** 298 | * UNKNOWN status won't be changed by synchronizeStatus, because it is the 299 | * 'largest' BatchStatus (will not downgrade). 300 | */ 301 | @Transactional 302 | @Test 303 | public void testSynchronizeStatusDowngrade() { 304 | 305 | JobExecution exec1 = new JobExecution(jobInstance); 306 | exec1.setStatus(BatchStatus.STARTED); 307 | dao.saveJobExecution(exec1); 308 | 309 | JobExecution exec2 = new JobExecution(jobInstance); 310 | Assert.state(exec1.getId() != null); 311 | exec2.setId(exec1.getId()); 312 | 313 | exec2.setStatus(BatchStatus.UNKNOWN); 314 | exec2.setVersion(7); 315 | Assert.state(!exec1.getVersion().equals(exec2.getVersion())); 316 | Assert.state(exec1.getStatus().isLessThan(exec2.getStatus())); 317 | 318 | dao.synchronizeStatus(exec2); 319 | 320 | assertEquals(exec1.getVersion(), exec2.getVersion()); 321 | assertEquals(BatchStatus.UNKNOWN, exec2.getStatus()); 322 | } 323 | 324 | /* 325 | * Check to make sure the executions are equal. Normally, comparing the id's 326 | * is sufficient. However, for testing purposes, especially of a DAO, we 327 | * need to make sure all the fields are being stored/retrieved correctly. 328 | */ 329 | 330 | private void assertExecutionsAreEqual(JobExecution lhs, JobExecution rhs) { 331 | 332 | assertEquals(lhs.getId(), rhs.getId()); 333 | assertEquals(lhs.getStartTime(), rhs.getStartTime()); 334 | assertEquals(lhs.getStatus(), rhs.getStatus()); 335 | assertEquals(lhs.getEndTime(), rhs.getEndTime()); 336 | assertEquals(lhs.getCreateTime(), rhs.getCreateTime()); 337 | assertEquals(lhs.getLastUpdated(), rhs.getLastUpdated()); 338 | assertEquals(lhs.getVersion(), rhs.getVersion()); 339 | } 340 | 341 | } 342 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/AbstractJobInstanceDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.DB; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.springframework.batch.core.JobInstance; 7 | import org.springframework.batch.core.JobParameters; 8 | import org.springframework.batch.core.JobParametersBuilder; 9 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 10 | import org.springframework.batch.mongo.config.Database; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | import static org.junit.Assert.*; 18 | 19 | public abstract class AbstractJobInstanceDaoTests { 20 | 21 | private static final long DATE = 777; 22 | 23 | protected JobInstanceDao dao; 24 | 25 | private String fooJob = "foo"; 26 | 27 | private JobParameters fooParams = new JobParametersBuilder().addString("stringKey", "stringValue").addLong( 28 | "longKey", Long.MAX_VALUE).addDouble("doubleKey", Double.MAX_VALUE).addDate("dateKey", new Date(DATE)) 29 | .toJobParameters(); 30 | 31 | protected abstract JobInstanceDao getJobInstanceDao(); 32 | 33 | @Autowired 34 | @Database(Database.Purpose.BATCH) 35 | protected DB db; 36 | 37 | @Before 38 | public void onSetUp() throws Exception { 39 | db.dropDatabase(); 40 | dao = getJobInstanceDao(); 41 | } 42 | 43 | /* 44 | * Create and retrieve a job instance. 45 | */ 46 | 47 | @Transactional 48 | @Test 49 | public void testCreateAndRetrieve() throws Exception { 50 | 51 | JobInstance fooInstance = dao.createJobInstance(fooJob, fooParams); 52 | assertNotNull(fooInstance.getId()); 53 | assertEquals(fooJob, fooInstance.getJobName()); 54 | assertEquals(fooParams, fooInstance.getJobParameters()); 55 | 56 | JobInstance retrievedInstance = dao.getJobInstance(fooJob, fooParams); 57 | JobParameters retrievedParams = retrievedInstance.getJobParameters(); 58 | assertEquals(fooInstance, retrievedInstance); 59 | assertEquals(fooJob, retrievedInstance.getJobName()); 60 | assertEquals(fooParams, retrievedParams); 61 | 62 | assertEquals(Long.MAX_VALUE, retrievedParams.getLong("longKey")); 63 | assertEquals(Double.MAX_VALUE, retrievedParams.getDouble("doubleKey"), 0.001); 64 | assertEquals("stringValue", retrievedParams.getString("stringKey")); 65 | assertEquals(new Date(DATE), retrievedParams.getDate("dateKey")); 66 | } 67 | 68 | /* 69 | * Create and retrieve a job instance. 70 | */ 71 | 72 | @Transactional 73 | @Test 74 | public void testCreateAndGetById() throws Exception { 75 | 76 | JobInstance fooInstance = dao.createJobInstance(fooJob, fooParams); 77 | assertNotNull(fooInstance.getId()); 78 | assertEquals(fooJob, fooInstance.getJobName()); 79 | assertEquals(fooParams, fooInstance.getJobParameters()); 80 | 81 | JobInstance retrievedInstance = dao.getJobInstance(fooInstance.getId()); 82 | JobParameters retrievedParams = retrievedInstance.getJobParameters(); 83 | assertEquals(fooInstance, retrievedInstance); 84 | assertEquals(fooJob, retrievedInstance.getJobName()); 85 | assertEquals(fooParams, retrievedParams); 86 | 87 | assertEquals(Long.MAX_VALUE, retrievedParams.getLong("longKey")); 88 | assertEquals(Double.MAX_VALUE, retrievedParams.getDouble("doubleKey"), 0.001); 89 | assertEquals("stringValue", retrievedParams.getString("stringKey")); 90 | assertEquals(new Date(DATE), retrievedParams.getDate("dateKey")); 91 | } 92 | 93 | /* 94 | * Create and retrieve a job instance. 95 | */ 96 | 97 | @Transactional 98 | @Test 99 | public void testGetMissingById() throws Exception { 100 | 101 | JobInstance retrievedInstance = dao.getJobInstance(1111111L); 102 | assertNull(retrievedInstance); 103 | 104 | } 105 | 106 | /* 107 | * Create and retrieve a job instance. 108 | */ 109 | 110 | @Transactional 111 | @Test 112 | public void testGetJobNames() throws Exception { 113 | 114 | testCreateAndRetrieve(); 115 | List jobNames = dao.getJobNames(); 116 | assertFalse(jobNames.isEmpty()); 117 | assertTrue(jobNames.contains(fooJob)); 118 | 119 | } 120 | 121 | /** 122 | * Create and retrieve a job instance. 123 | * 124 | * @throws Exception when shit happens 125 | */ 126 | @Transactional 127 | @Test 128 | public void testGetLastInstances() throws Exception { 129 | 130 | testCreateAndRetrieve(); 131 | 132 | // unrelated job instance that should be ignored by the query 133 | dao.createJobInstance("anotherJob", new JobParameters()); 134 | 135 | // we need two instances of the same job to check ordering 136 | dao.createJobInstance(fooJob, new JobParameters()); 137 | 138 | List jobInstances = dao.getJobInstances(fooJob, 0, 2); 139 | assertEquals(2, jobInstances.size()); 140 | assertEquals(fooJob, jobInstances.get(0).getJobName()); 141 | assertEquals(fooJob, jobInstances.get(1).getJobName()); 142 | assertEquals(Integer.valueOf(0), jobInstances.get(0).getVersion()); 143 | assertEquals(Integer.valueOf(0), jobInstances.get(1).getVersion()); 144 | 145 | assertTrue("Last instance should be first on the list", jobInstances.get(0).getId() > jobInstances.get(1).getId()); 146 | 147 | } 148 | 149 | /** 150 | * Create and retrieve a job instance. 151 | * 152 | * @throws Exception when shit happens 153 | */ 154 | @Transactional 155 | @Test 156 | public void testGetLastInstancesPaged() throws Exception { 157 | 158 | testCreateAndRetrieve(); 159 | 160 | // unrelated job instance that should be ignored by the query 161 | dao.createJobInstance("anotherJob", new JobParameters()); 162 | 163 | // we need two instances of the same job to check ordering 164 | dao.createJobInstance(fooJob, new JobParameters()); 165 | 166 | List jobInstances = dao.getJobInstances(fooJob, 1, 2); 167 | assertEquals(1, jobInstances.size()); 168 | assertEquals(fooJob, jobInstances.get(0).getJobName()); 169 | assertEquals(Integer.valueOf(0), jobInstances.get(0).getVersion()); 170 | 171 | } 172 | 173 | /** 174 | * Create and retrieve a job instance. 175 | * 176 | * @throws Exception when shit happens 177 | */ 178 | @Transactional 179 | @Test 180 | public void testGetLastInstancesPastEnd() throws Exception { 181 | 182 | testCreateAndRetrieve(); 183 | 184 | // unrelated job instance that should be ignored by the query 185 | dao.createJobInstance("anotherJob", new JobParameters()); 186 | 187 | // we need two instances of the same job to check ordering 188 | dao.createJobInstance(fooJob, new JobParameters()); 189 | 190 | List jobInstances = dao.getJobInstances(fooJob, 4, 2); 191 | assertEquals(0, jobInstances.size()); 192 | 193 | } 194 | 195 | /** 196 | * Trying to create instance twice for the same job+parameters causes error 197 | */ 198 | @Transactional 199 | @Test(expected = IllegalStateException.class) 200 | public void testCreateDuplicateInstance() { 201 | dao.createJobInstance(fooJob, fooParams); 202 | dao.createJobInstance(fooJob, fooParams); 203 | } 204 | 205 | @Transactional 206 | @Test 207 | public void testCreationAddsVersion() { 208 | 209 | JobInstance jobInstance = new JobInstance((long) 1, new JobParameters(), "testVersionAndId"); 210 | 211 | assertNull(jobInstance.getVersion()); 212 | 213 | jobInstance = dao.createJobInstance("testVersion", new JobParameters()); 214 | 215 | assertNotNull(jobInstance.getVersion()); 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/AbstractStepExecutionDaoTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006-2007 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.batch.mongo.dao; 18 | 19 | import com.mongodb.DB; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.springframework.batch.core.*; 23 | import org.springframework.batch.core.repository.JobRepository; 24 | import org.springframework.batch.core.repository.dao.StepExecutionDao; 25 | import org.springframework.batch.mongo.config.Database; 26 | import org.springframework.batch.mongo.step.StepSupport; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.dao.OptimisticLockingFailureException; 29 | import org.springframework.transaction.annotation.Transactional; 30 | 31 | import java.util.Collection; 32 | import java.util.Date; 33 | 34 | import static org.junit.Assert.*; 35 | 36 | /** 37 | * Tests for {@link StepExecutionDao} implementations. 38 | * 39 | * @see #getStepExecutionDao() 40 | */ 41 | public abstract class AbstractStepExecutionDaoTests { 42 | 43 | protected StepExecutionDao dao; 44 | 45 | protected JobInstance jobInstance; 46 | 47 | protected JobExecution jobExecution; 48 | 49 | protected Step step; 50 | 51 | protected StepExecution stepExecution; 52 | 53 | protected JobRepository repository; 54 | 55 | @Autowired 56 | @Database(Database.Purpose.BATCH) 57 | protected DB db; 58 | 59 | /** 60 | * @return {@link StepExecutionDao} implementation ready for use. 61 | */ 62 | protected abstract StepExecutionDao getStepExecutionDao(); 63 | 64 | /** 65 | * @return {@link JobRepository} that uses the stepExecution dao. 66 | */ 67 | protected abstract JobRepository getJobRepository(); 68 | 69 | @Before 70 | public void onSetUp() throws Exception { 71 | db.dropDatabase(); 72 | repository = getJobRepository(); 73 | jobExecution = repository.createJobExecution("job", new JobParameters()); 74 | jobInstance = jobExecution.getJobInstance(); 75 | step = new StepSupport("foo"); 76 | stepExecution = new StepExecution(step.getName(), jobExecution); 77 | dao = getStepExecutionDao(); 78 | } 79 | 80 | @Transactional 81 | @Test 82 | public void testSaveExecutionAssignsIdAndVersion() throws Exception { 83 | 84 | assertNull(stepExecution.getId()); 85 | assertNull(stepExecution.getVersion()); 86 | dao.saveStepExecution(stepExecution); 87 | assertNotNull(stepExecution.getId()); 88 | assertNotNull(stepExecution.getVersion()); 89 | } 90 | 91 | @Transactional 92 | @Test 93 | public void testSaveAndGetExecution() { 94 | 95 | stepExecution.setStatus(BatchStatus.STARTED); 96 | stepExecution.setReadSkipCount(7); 97 | stepExecution.setProcessSkipCount(2); 98 | stepExecution.setWriteSkipCount(5); 99 | stepExecution.setProcessSkipCount(11); 100 | stepExecution.setRollbackCount(3); 101 | stepExecution.setLastUpdated(new Date(System.currentTimeMillis())); 102 | stepExecution.setReadCount(17); 103 | stepExecution.setFilterCount(15); 104 | stepExecution.setWriteCount(13); 105 | dao.saveStepExecution(stepExecution); 106 | 107 | StepExecution retrieved = dao.getStepExecution(jobExecution, stepExecution.getId()); 108 | 109 | assertStepExecutionsAreEqual(stepExecution, retrieved); 110 | assertNotNull(retrieved.getVersion()); 111 | assertNotNull(retrieved.getJobExecution()); 112 | assertNotNull(retrieved.getJobExecution().getId()); 113 | assertNotNull(retrieved.getJobExecution().getJobId()); 114 | assertNotNull(retrieved.getJobExecution().getJobInstance()); 115 | 116 | } 117 | 118 | @Transactional 119 | @Test 120 | public void testSaveAndGetNonExistentExecution() { 121 | assertNull(dao.getStepExecution(jobExecution, 45677L)); 122 | } 123 | 124 | @Transactional 125 | @Test 126 | public void testSaveAndFindExecution() { 127 | 128 | stepExecution.setStatus(BatchStatus.STARTED); 129 | stepExecution.setReadSkipCount(7); 130 | stepExecution.setWriteSkipCount(5); 131 | stepExecution.setRollbackCount(3); 132 | dao.saveStepExecution(stepExecution); 133 | 134 | dao.addStepExecutions(jobExecution); 135 | Collection retrieved = jobExecution.getStepExecutions(); 136 | assertStepExecutionsAreEqual(stepExecution, retrieved.iterator().next()); 137 | } 138 | 139 | @Transactional 140 | @Test 141 | public void testGetForNotExistingJobExecution() { 142 | assertNull(dao.getStepExecution(new JobExecution(jobInstance, (long) 777), 11L)); 143 | } 144 | 145 | /** 146 | * To-be-saved execution must not already have an id. 147 | */ 148 | @Transactional 149 | @Test(expected = IllegalArgumentException.class) 150 | public void testSaveExecutionWithIdAlreadySet() { 151 | stepExecution.setId((long) 7); 152 | dao.saveStepExecution(stepExecution); 153 | } 154 | 155 | /** 156 | * To-be-saved execution must not already have a version. 157 | */ 158 | @Transactional 159 | @Test(expected = IllegalArgumentException.class) 160 | public void testSaveExecutionWithVersionAlreadySet() { 161 | stepExecution.incrementVersion(); 162 | dao.saveStepExecution(stepExecution); 163 | } 164 | 165 | /** 166 | * Update and retrieve updated StepExecution - make sure the update is 167 | * reflected as expected and version number has been incremented 168 | */ 169 | @Transactional 170 | @Test 171 | public void testUpdateExecution() { 172 | stepExecution.setStatus(BatchStatus.STARTED); 173 | dao.saveStepExecution(stepExecution); 174 | Integer versionAfterSave = stepExecution.getVersion(); 175 | 176 | stepExecution.setStatus(BatchStatus.ABANDONED); 177 | stepExecution.setLastUpdated(new Date(System.currentTimeMillis())); 178 | dao.updateStepExecution(stepExecution); 179 | assertEquals(versionAfterSave + 1, stepExecution.getVersion().intValue()); 180 | 181 | StepExecution retrieved = dao.getStepExecution(jobExecution, stepExecution.getId()); 182 | assertEquals(stepExecution, retrieved); 183 | assertEquals(stepExecution.getLastUpdated(), retrieved.getLastUpdated()); 184 | assertEquals(BatchStatus.ABANDONED, retrieved.getStatus()); 185 | } 186 | 187 | /** 188 | * Exception should be raised when the version of update argument doesn't 189 | * match the version of persisted entity. 190 | */ 191 | @Transactional 192 | @Test(expected = OptimisticLockingFailureException.class) 193 | public void testConcurrentModificationException() { 194 | step = new StepSupport("foo"); 195 | 196 | StepExecution exec1 = new StepExecution(step.getName(), jobExecution); 197 | dao.saveStepExecution(exec1); 198 | 199 | StepExecution exec2 = new StepExecution(step.getName(), jobExecution); 200 | exec2.setId(exec1.getId()); 201 | 202 | exec2.incrementVersion(); 203 | assertEquals(new Integer(0), exec1.getVersion()); 204 | assertEquals(exec1.getVersion(), exec2.getVersion()); 205 | 206 | dao.updateStepExecution(exec1); 207 | assertEquals(new Integer(1), exec1.getVersion()); 208 | dao.updateStepExecution(exec2); 209 | 210 | } 211 | 212 | @Test 213 | public void testGetStepExecutionsWhenNoneExist() throws Exception { 214 | int count = jobExecution.getStepExecutions().size(); 215 | dao.addStepExecutions(jobExecution); 216 | assertEquals("Incorrect size of collection", count, jobExecution.getStepExecutions().size()); 217 | } 218 | 219 | private void assertStepExecutionsAreEqual(StepExecution expected, StepExecution actual) { 220 | assertEquals(expected.getId(), actual.getId()); 221 | assertEquals(expected.getStartTime(), actual.getStartTime()); 222 | assertEquals(expected.getEndTime(), actual.getEndTime()); 223 | assertEquals(expected.getSkipCount(), actual.getSkipCount()); 224 | assertEquals(expected.getCommitCount(), actual.getCommitCount()); 225 | assertEquals(expected.getReadCount(), actual.getReadCount()); 226 | assertEquals(expected.getWriteCount(), actual.getWriteCount()); 227 | assertEquals(expected.getFilterCount(), actual.getFilterCount()); 228 | assertEquals(expected.getWriteSkipCount(), actual.getWriteSkipCount()); 229 | assertEquals(expected.getReadSkipCount(), actual.getReadSkipCount()); 230 | assertEquals(expected.getProcessSkipCount(), actual.getProcessSkipCount()); 231 | assertEquals(expected.getRollbackCount(), actual.getRollbackCount()); 232 | assertEquals(expected.getExitStatus(), actual.getExitStatus()); 233 | assertEquals(expected.getLastUpdated(), actual.getLastUpdated()); 234 | assertEquals(expected.getExitStatus(), actual.getExitStatus()); 235 | assertEquals(expected.getJobExecutionId(), actual.getJobExecutionId()); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/MongoDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.DB; 4 | import junit.framework.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.batch.mongo.config.Database; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | 14 | /** 15 | * Created by IntelliJ IDEA. 16 | * 17 | * @author JBaruch 18 | * @since 20-Apr-2010 19 | */ 20 | @ContextConfiguration(locations = {"classpath:application-config.xml"}) 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | public class MongoDaoTests { 23 | 24 | @Autowired 25 | @Database(Database.Purpose.BATCH) 26 | private DB batchDB; 27 | 28 | @Autowired 29 | private MongoJobExecutionDao dao; 30 | 31 | @Before 32 | public void setUp() throws Exception { 33 | batchDB.dropDatabase(); 34 | } 35 | 36 | @Test 37 | public void testGetNextId() { 38 | for (long i = 1; i <= 100; i++) { 39 | long id = dao.getNextId(MongoJobExecutionDao.class.getSimpleName()); 40 | Assert.assertEquals(i, id); 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/MongoExecutionContextDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.batch.core.repository.dao.ExecutionContextDao; 5 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 6 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 7 | import org.springframework.batch.core.repository.dao.StepExecutionDao; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | 13 | /** 14 | * Created by IntelliJ IDEA. 15 | * 16 | * @author Baruch S. 17 | * @since Apr 22, 2010 18 | */ 19 | 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @ContextConfiguration(locations = {"classpath:application-config.xml"}) 22 | public class MongoExecutionContextDaoTests extends AbstractExecutionContextDaoTests { 23 | @Autowired 24 | private JobExecutionDao jobExecutionDao; 25 | @Autowired 26 | private JobInstanceDao jobInstanceDao; 27 | @Autowired 28 | private StepExecutionDao stepExecutionDao; 29 | @Autowired 30 | private ExecutionContextDao executionContextDao; 31 | 32 | @Override 33 | protected JobExecutionDao getJobExecutionDao() { 34 | return jobExecutionDao; 35 | } 36 | 37 | @Override 38 | protected JobInstanceDao getJobInstanceDao() { 39 | return jobInstanceDao; 40 | } 41 | 42 | @Override 43 | protected StepExecutionDao getStepExecutionDao() { 44 | return stepExecutionDao; 45 | } 46 | 47 | @Override 48 | protected ExecutionContextDao getExecutionContextDao() { 49 | return executionContextDao; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/MongoJobDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import com.mongodb.BasicDBObject; 4 | import com.mongodb.DBObject; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.batch.core.ExitStatus; 8 | import org.springframework.batch.core.JobExecution; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | 15 | @RunWith(SpringJUnit4ClassRunner.class) 16 | @ContextConfiguration(locations = {"classpath:application-config.xml"}) 17 | public class MongoJobDaoTests extends AbstractJobDaoTests { 18 | 19 | public static final String LONG_STRING = "A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String A very long String "; 20 | 21 | @Transactional 22 | @Test 23 | public void testUpdateJobExecutionWithLongExitCode() { 24 | 25 | jobExecution.setExitStatus(ExitStatus.COMPLETED 26 | .addExitDescription(LONG_STRING)); 27 | jobExecutionDao.updateJobExecution(jobExecution); 28 | 29 | 30 | DBObject dbObject = db.getCollection(JobExecution.class.getSimpleName()).findOne(new BasicDBObject(MongoJobInstanceDao.JOB_INSTANCE_ID_KEY, jobInstance.getId())); 31 | assertEquals(LONG_STRING, dbObject.get(AbstractMongoDao.EXIT_MESSAGE_KEY)); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/MongoJobExecutionDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 5 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | 11 | /** 12 | * Created by IntelliJ IDEA. 13 | * 14 | * @author Baruch S. 15 | * @since Apr 22, 2010 16 | */ 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @ContextConfiguration(locations = {"classpath:application-config.xml"}) 19 | public class MongoJobExecutionDaoTests extends AbstractJobExecutionDaoTests { 20 | 21 | @Autowired 22 | private JobExecutionDao jobExecutionDao; 23 | @Autowired 24 | private JobInstanceDao jobInstanceDao; 25 | 26 | @Override 27 | protected JobExecutionDao getJobExecutionDao() { 28 | return jobExecutionDao; 29 | } 30 | 31 | @Override 32 | protected JobInstanceDao getJobInstanceDao() { 33 | return jobInstanceDao; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/MongoJobInstanceDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.batch.core.JobExecution; 6 | import org.springframework.batch.core.JobInstance; 7 | import org.springframework.batch.core.JobParameters; 8 | import org.springframework.batch.core.JobParametersBuilder; 9 | import org.springframework.batch.core.repository.dao.JobExecutionDao; 10 | import org.springframework.batch.core.repository.dao.JobInstanceDao; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.test.context.ContextConfiguration; 13 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 14 | 15 | import java.math.BigInteger; 16 | import java.security.MessageDigest; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @ContextConfiguration(locations = {"classpath:application-config.xml"}) 22 | public class MongoJobInstanceDaoTests extends AbstractJobInstanceDaoTests { 23 | 24 | @Autowired 25 | private JobExecutionDao jobExecutionDao; 26 | @Autowired 27 | private JobInstanceDao jobInstanceDao; 28 | 29 | protected JobInstanceDao getJobInstanceDao() { 30 | return jobInstanceDao; 31 | } 32 | 33 | @Test 34 | public void testFindJobInstanceByExecution() { 35 | 36 | JobInstance jobInstance = dao.createJobInstance("testInstance", 37 | new JobParameters()); 38 | JobExecution jobExecution = new JobExecution(jobInstance, 2L); 39 | jobExecutionDao.saveJobExecution(jobExecution); 40 | 41 | JobInstance returnedInstance = dao.getJobInstance(jobExecution); 42 | assertEquals(jobInstance, returnedInstance); 43 | } 44 | 45 | @Test 46 | public void testCreateJobKey() { 47 | 48 | MongoJobInstanceDao jdbcDao = (MongoJobInstanceDao) dao; 49 | JobParameters jobParameters = new JobParametersBuilder().addString( 50 | "foo", "bar").addString("bar", "foo").toJobParameters(); 51 | String key = jdbcDao.createJobKey(jobParameters); 52 | assertEquals(32, key.length()); 53 | 54 | } 55 | 56 | @Test 57 | public void testCreateJobKeyOrdering() { 58 | 59 | MongoJobInstanceDao jdbcDao = (MongoJobInstanceDao) dao; 60 | JobParameters jobParameters1 = new JobParametersBuilder().addString( 61 | "foo", "bar").addString("bar", "foo").toJobParameters(); 62 | String key1 = jdbcDao.createJobKey(jobParameters1); 63 | JobParameters jobParameters2 = new JobParametersBuilder().addString( 64 | "bar", "foo").addString("foo", "bar").toJobParameters(); 65 | String key2 = jdbcDao.createJobKey(jobParameters2); 66 | assertEquals(key1, key2); 67 | 68 | } 69 | 70 | @Test 71 | public void testHexing() throws Exception { 72 | MessageDigest digest = MessageDigest.getInstance("MD5"); 73 | byte[] bytes = digest.digest("f78spx".getBytes("UTF-8")); 74 | StringBuffer output = new StringBuffer(); 75 | for (byte bite : bytes) { 76 | output.append(String.format("%02x", bite)); 77 | } 78 | assertEquals("Wrong hash: " + output, 32, output.length()); 79 | String value = String.format("%032x", new BigInteger(1, bytes)); 80 | assertEquals("Wrong hash: " + value, 32, value.length()); 81 | assertEquals(value, output.toString()); 82 | } 83 | } -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/dao/MongoStepExecutionDaoTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.dao; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.batch.core.repository.JobRepository; 5 | import org.springframework.batch.core.repository.dao.StepExecutionDao; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | 11 | /** 12 | * Created by IntelliJ IDEA. 13 | * 14 | * @author Baruch S. 15 | * @since Apr 22, 2010 16 | */ 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @ContextConfiguration(locations = {"classpath:application-config.xml"}) 19 | public class MongoStepExecutionDaoTests extends AbstractStepExecutionDaoTests { 20 | 21 | @Autowired 22 | private StepExecutionDao stepExecutionDao; 23 | 24 | @Autowired 25 | private JobRepository jobRepository; 26 | 27 | @Override 28 | protected StepExecutionDao getStepExecutionDao() { 29 | return stepExecutionDao; 30 | } 31 | 32 | @Override 33 | protected JobRepository getJobRepository() { 34 | return jobRepository; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/example/ExampleItemReader.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.example; 2 | 3 | import org.springframework.batch.item.ItemReader; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * {@link ItemReader} with hard-coded input data. 8 | */ 9 | @Component 10 | public class ExampleItemReader implements ItemReader { 11 | 12 | private String[] input = {"Hello world!", null}; 13 | 14 | private int index = 0; 15 | 16 | /** 17 | * Reads next record from input 18 | */ 19 | public String read() throws Exception { 20 | if (index < input.length) { 21 | return input[index++]; 22 | } else { 23 | return null; 24 | } 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/example/ExampleItemReaderTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.example; 2 | 3 | import org.junit.Test; 4 | 5 | import static junit.framework.Assert.assertEquals; 6 | 7 | public class ExampleItemReaderTests { 8 | 9 | private ExampleItemReader reader = new ExampleItemReader(); 10 | 11 | @Test 12 | public void testReadOnce() throws Exception { 13 | assertEquals("Hello world!", reader.read()); 14 | } 15 | 16 | @Test 17 | public void testReadTwice() throws Exception { 18 | reader.read(); 19 | assertEquals(null, reader.read()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/example/ExampleItemWriter.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.example; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.batch.item.ItemWriter; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | 10 | 11 | /** 12 | * Dummy {@link ItemWriter} which only logs data it receives. 13 | */ 14 | @Component 15 | public class ExampleItemWriter implements ItemWriter { 16 | 17 | private static final Log log = LogFactory.getLog(ExampleItemWriter.class); 18 | 19 | /** 20 | * @see ItemWriter#write(List) 21 | */ 22 | public void write(List data) throws Exception { 23 | log.info(data); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/example/ExampleItemWriterTests.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.example; 2 | 3 | import org.junit.Test; 4 | 5 | public class ExampleItemWriterTests { 6 | 7 | private ExampleItemWriter writer = new ExampleItemWriter(); 8 | 9 | @Test 10 | public void testWrite() throws Exception { 11 | writer.write(null); // nothing bad happens 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/example/ExampleJobConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006-2007 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.batch.mongo.example; 17 | 18 | import com.mongodb.DB; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.springframework.batch.core.BatchStatus; 23 | import org.springframework.batch.core.JobExecution; 24 | import org.springframework.batch.core.JobInstance; 25 | import org.springframework.batch.core.launch.JobOperator; 26 | import org.springframework.batch.mongo.config.Database; 27 | import org.springframework.batch.test.JobLauncherTestUtils; 28 | import org.springframework.beans.factory.annotation.Autowired; 29 | import org.springframework.test.context.ContextConfiguration; 30 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 31 | 32 | import static org.junit.Assert.assertEquals; 33 | import static org.junit.Assert.assertTrue; 34 | 35 | @ContextConfiguration(locations = {"classpath:application-config.xml"}) 36 | @RunWith(SpringJUnit4ClassRunner.class) 37 | public class ExampleJobConfigurationTests { 38 | 39 | @Autowired 40 | private JobOperator jobOperator; 41 | 42 | @Autowired 43 | private JobLauncherTestUtils jobLauncherTestUtils; 44 | 45 | @Autowired 46 | @Database(Database.Purpose.BATCH) 47 | private DB batchDB; 48 | 49 | @Before 50 | public void setUp() { 51 | batchDB.dropDatabase(); 52 | } 53 | 54 | /** 55 | * Create a unique job instance and check it's execution completes 56 | * successfully - uses the convenience methods provided by the testing 57 | * superclass. 58 | * 59 | * @throws Exception when shit happens 60 | */ 61 | @Test 62 | public void testLaunchJob() throws Exception { 63 | 64 | JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobLauncherTestUtils.getUniqueJobParameters()); 65 | assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); 66 | } 67 | 68 | /** 69 | * Execute a fresh {@link JobInstance} using {@link JobOperator} - closer to 70 | * a remote invocation scenario. 71 | * 72 | * @throws Exception when shit happens 73 | */ 74 | @Test 75 | public void testLaunchByJobOperator() throws Exception { 76 | 77 | // assumes the job has a JobIncrementer set 78 | long jobExecutionId = jobOperator.startNextInstance(jobLauncherTestUtils.getJob().getName()); 79 | 80 | // no need to wait for job completion in this case, the job is launched 81 | // synchronously 82 | 83 | String result = jobOperator.getSummary(jobExecutionId); 84 | assertTrue(result.contains("status=" + BatchStatus.COMPLETED)); 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/step/StepSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006-2007 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.batch.mongo.step; 17 | 18 | import org.springframework.batch.core.JobInterruptedException; 19 | import org.springframework.batch.core.Step; 20 | import org.springframework.batch.core.StepExecution; 21 | import org.springframework.batch.core.UnexpectedJobExecutionException; 22 | import org.springframework.beans.factory.BeanNameAware; 23 | 24 | /** 25 | * Basic no-op support implementation for use as base class for {@link Step}. Implements {@link BeanNameAware} so that 26 | * if no name is provided explicitly it will be inferred from the bean definition in Spring configuration. 27 | * 28 | * @author Dave Syer 29 | */ 30 | public class StepSupport implements Step, BeanNameAware { 31 | 32 | private String name; 33 | 34 | private int startLimit = Integer.MAX_VALUE; 35 | 36 | private boolean allowStartIfComplete; 37 | 38 | /** 39 | * Default constructor for {@link StepSupport}. 40 | */ 41 | public StepSupport() { 42 | super(); 43 | } 44 | 45 | /** 46 | * @param string 47 | */ 48 | public StepSupport(String string) { 49 | super(); 50 | this.name = string; 51 | } 52 | 53 | public String getName() { 54 | return this.name; 55 | } 56 | 57 | /** 58 | * Set the name property if it is not already set. Because of the order of the callbacks in a Spring container the 59 | * name property will be set first if it is present. Care is needed with bean definition inheritance - if a parent 60 | * bean has a name, then its children need an explicit name as well, otherwise they will not be unique. 61 | * 62 | * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) 63 | */ 64 | public void setBeanName(String name) { 65 | if (this.name == null) { 66 | this.name = name; 67 | } 68 | } 69 | 70 | /** 71 | * Set the name property. Always overrides the default value if this object is a Spring bean. 72 | * 73 | * @see #setBeanName(java.lang.String) 74 | */ 75 | public void setName(String name) { 76 | this.name = name; 77 | } 78 | 79 | public int getStartLimit() { 80 | return this.startLimit; 81 | } 82 | 83 | /** 84 | * Public setter for the startLimit. 85 | * 86 | * @param startLimit the startLimit to set 87 | */ 88 | public void setStartLimit(int startLimit) { 89 | this.startLimit = startLimit; 90 | } 91 | 92 | public boolean isAllowStartIfComplete() { 93 | return this.allowStartIfComplete; 94 | } 95 | 96 | /** 97 | * Public setter for the shouldAllowStartIfComplete. 98 | * 99 | * @param allowStartIfComplete the shouldAllowStartIfComplete to set 100 | */ 101 | public void setAllowStartIfComplete(boolean allowStartIfComplete) { 102 | this.allowStartIfComplete = allowStartIfComplete; 103 | } 104 | 105 | /** 106 | * Not supported but provided so that tests can easily create a step. 107 | * 108 | * @throws UnsupportedOperationException always 109 | * @see org.springframework.batch.core.Step#execute(org.springframework.batch.core.StepExecution) 110 | */ 111 | public void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException { 112 | throw new UnsupportedOperationException( 113 | "Cannot process a StepExecution. Use a smarter subclass of StepSupport."); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/batch/mongo/util/Shutdown.java: -------------------------------------------------------------------------------- 1 | package org.springframework.batch.mongo.util; 2 | 3 | import com.mongodb.BasicDBObject; 4 | import com.mongodb.DB; 5 | import com.mongodb.Mongo; 6 | import org.springframework.context.support.ClassPathXmlApplicationContext; 7 | 8 | import java.nio.BufferUnderflowException; 9 | 10 | /** 11 | * Created by IntelliJ IDEA. 12 | * 13 | * @author JBaruch 14 | * @since 28-Apr-2010 15 | */ 16 | public class Shutdown { 17 | public static void main(String[] args) { 18 | ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application-config.xml"); 19 | Mongo mongo = ctx.getBean(Mongo.class); 20 | DB adminDb = mongo.getDB("admin"); 21 | try { 22 | adminDb.command(new BasicDBObject("shutdown", Boolean.TRUE)); 23 | } catch (BufferUnderflowException ignored) { 24 | } finally { 25 | ctx.close(); 26 | } 27 | } 28 | } 29 | --------------------------------------------------------------------------------