/*
* Copyright 2008-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.repository.dao;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.util.Collections;
import java.util.Date;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
@RunWith(JUnit4.class)
public class MapJobExecutionDaoTests extends AbstractJobExecutionDaoTests {
@Override
protected JobExecutionDao getJobExecutionDao() {
return new MapJobExecutionDao();
}
@Override
protected JobInstanceDao getJobInstanceDao() {
return new MapJobInstanceDao();
}
/**
* Modifications to saved entity do not affect the persisted object.
*/
@Test
public void testPersistentCopy() {
JobExecutionDao tested = new MapJobExecutionDao();
JobExecution jobExecution = new JobExecution(new JobInstance((long) 1, "mapJob"), new JobParameters());
assertNull(jobExecution.getStartTime());
tested.saveJobExecution(jobExecution);
jobExecution.setStartTime(new Date());
JobExecution retrieved = tested.getJobExecution(jobExecution.getId());
assertNull(retrieved.getStartTime());
tested.updateJobExecution(jobExecution);
jobExecution.setEndTime(new Date());
assertNull(retrieved.getEndTime());
}
/**
* Verify that the ids are properly generated even under heavy concurrent load
*/
@Test
public void testConcurrentSaveJobExecution() throws Exception {
final int iterations = 100;
// Object under test
final JobExecutionDao tested = new MapJobExecutionDao();
// Support objects for this testing
final CountDownLatch latch = new CountDownLatch(1);
final SortedSet<Long> ids = Collections.synchronizedSortedSet(new TreeSet<Long>()); // TODO Change to SkipList w/JDK6
final AtomicReference<Exception> exception = new AtomicReference<Exception>(null);
// Implementation of the high-concurrency code
final Runnable codeUnderTest = new Runnable() {
@Override
public void run() {
try {
JobExecution jobExecution = new JobExecution(new JobInstance((long) -1, "mapJob"), new JobParameters());
latch.await();
tested.saveJobExecution(jobExecution);
ids.add(jobExecution.getId());
} catch(Exception e) {
exception.set(e);
}
}
};
// Create the threads
final Thread[] threads = new Thread[iterations];
for(int i = 0; i < iterations; i++) {
Thread t = new Thread(codeUnderTest, "Map Job Thread #" + (i+1));
t.setPriority(Thread.MAX_PRIORITY);
t.setDaemon(true);
t.start();
Thread.yield();
threads[i] = t;
}
// Let the high concurrency abuse begin!
do { latch.countDown(); } while(latch.getCount() > 0);
for(Thread t : threads) { t.join(); }
// Ensure no general exceptions arose
if(exception.get() != null) {
throw new RuntimeException("Exception occurred under high concurrency usage", exception.get());
}
// Validate the ids: we'd expect one of these three things to fail
if(ids.size() < iterations) {
fail("Duplicate id generated during high concurrency usage");
}
if(ids.first() < 0) {
fail("Generated an id less than zero during high concurrency usage: " + ids.first());
}
if(ids.last() > iterations) {
fail("Generated an id larger than expected during high concurrency usage: " + ids.last());
}
}
}