Batch2 test refactor only (#5823)

* refactoring

* test refactor only

* spotless

* bumping version

---------

Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-mbp.home>
This commit is contained in:
TipzCM 2024-04-05 21:39:03 -04:00 committed by GitHub
parent 620b46dd0a
commit 5d55594a73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
90 changed files with 667 additions and 637 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
@ -12,7 +12,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,6 +20,7 @@ import ca.uhn.fhir.jpa.entity.Batch2WorkChunkEntity;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.util.JsonUtil;
import ca.uhn.hapi.fhir.batch2.test.AbstractIJobPersistenceSpecificationTest;
import ca.uhn.hapi.fhir.batch2.test.configs.SpyOverrideConfig;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import org.junit.jupiter.api.MethodOrderer;
@ -30,6 +31,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
@ -60,6 +62,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.MethodName.class)
@Import(SpyOverrideConfig.class)
public class JpaJobPersistenceImplTest extends BaseJpaR4Test {
public static final String JOB_DEFINITION_ID = "definition-id";
@ -323,14 +326,19 @@ public class JpaJobPersistenceImplTest extends BaseJpaR4Test {
class Batch2SpecTest extends AbstractIJobPersistenceSpecificationTest {
@Override
protected PlatformTransactionManager getTxManager() {
public PlatformTransactionManager getTxManager() {
return JpaJobPersistenceImplTest.this.getTxManager();
}
@Override
protected WorkChunk freshFetchWorkChunk(String chunkId) {
public WorkChunk freshFetchWorkChunk(String chunkId) {
return JpaJobPersistenceImplTest.this.freshFetchWorkChunk(chunkId);
}
@Override
public void runMaintenancePass() {
myBatch2JobHelper.forceRunMaintenancePass();
}
}
@Test

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -21,7 +21,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
</dependency>
<dependency>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,30 +20,28 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.api.IJobMaintenanceService;
import ca.uhn.fhir.batch2.api.IJobPersistence;
import ca.uhn.fhir.batch2.api.RunOutcome;
import ca.uhn.fhir.batch2.channel.BatchJobSender;
import ca.uhn.fhir.batch2.coordinator.JobDefinitionRegistry;
import ca.uhn.fhir.batch2.maintenance.JobChunkProgressAccumulator;
import ca.uhn.fhir.batch2.maintenance.JobInstanceProcessor;
import ca.uhn.fhir.batch2.model.JobDefinition;
import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.batch2.model.JobWorkNotification;
import ca.uhn.fhir.batch2.model.StatusEnum;
import ca.uhn.fhir.batch2.model.WorkChunk;
import ca.uhn.fhir.batch2.model.WorkChunkCompletionEvent;
import ca.uhn.fhir.batch2.model.WorkChunkCreateEvent;
import ca.uhn.fhir.batch2.model.WorkChunkErrorEvent;
import ca.uhn.fhir.batch2.model.WorkChunkStatusEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.hapi.fhir.batch2.test.support.TestJobParameters;
import ca.uhn.hapi.fhir.batch2.test.support.TestJobStep2InputType;
import ca.uhn.hapi.fhir.batch2.test.support.TestJobStep3InputType;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.BeforeEach;
import ca.uhn.test.concurrency.PointcutLatch;
import jakarta.annotation.Nonnull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -53,580 +51,118 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import jakarta.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.theInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Specification tests for batch2 storage and event system.
* These tests are abstract, and do not depend on JPA.
* Test setups should use the public batch2 api to create scenarios.
*/
public abstract class AbstractIJobPersistenceSpecificationTest {
private static final Logger ourLog = LoggerFactory.getLogger(AbstractIJobPersistenceSpecificationTest.class);
public abstract class AbstractIJobPersistenceSpecificationTest implements IInProgressActionsTests, IInstanceStateTransitions, ITestFixture, WorkChunkTestConstants {
public static final String JOB_DEFINITION_ID = "definition-id";
public static final String TARGET_STEP_ID = "step-id";
public static final String DEF_CHUNK_ID = "definition-chunkId";
public static final String STEP_CHUNK_ID = "step-chunkId";
public static final int JOB_DEF_VER = 1;
public static final int SEQUENCE_NUMBER = 1;
public static final String CHUNK_DATA = "{\"key\":\"value\"}";
public static final String ERROR_MESSAGE_A = "This is an error message: A";
public static final String ERROR_MESSAGE_B = "This is a different error message: B";
public static final String ERROR_MESSAGE_C = "This is a different error message: C";
private static final Logger ourLog = LoggerFactory.getLogger(AbstractIJobPersistenceSpecificationTest.class);
@Autowired
private IJobPersistence mySvc;
@Nested
class WorkChunkStorage {
@Autowired
private JobDefinitionRegistry myJobDefinitionRegistry;
@Test
public void testStoreAndFetchWorkChunk_NoData() {
JobInstance instance = createInstance();
String instanceId = mySvc.storeNewInstance(instance);
@Autowired
private PlatformTransactionManager myTransactionManager;
String id = storeWorkChunk(JOB_DEFINITION_ID, TARGET_STEP_ID, instanceId, 0, null);
@Autowired
private IJobMaintenanceService myMaintenanceService;
WorkChunk chunk = mySvc.onWorkChunkDequeue(id).orElseThrow(IllegalArgumentException::new);
assertNull(chunk.getData());
}
@Autowired
private BatchJobSender myBatchJobSender;
@Test
public void testStoreAndFetchWorkChunk_WithData() {
JobInstance instance = createInstance();
String instanceId = mySvc.storeNewInstance(instance);
String id = storeWorkChunk(JOB_DEFINITION_ID, TARGET_STEP_ID, instanceId, 0, CHUNK_DATA);
assertNotNull(id);
runInTransaction(() -> assertEquals(WorkChunkStatusEnum.QUEUED, freshFetchWorkChunk(id).getStatus()));
WorkChunk chunk = mySvc.onWorkChunkDequeue(id).orElseThrow(IllegalArgumentException::new);
assertEquals(36, chunk.getInstanceId().length());
assertEquals(JOB_DEFINITION_ID, chunk.getJobDefinitionId());
assertEquals(JOB_DEF_VER, chunk.getJobDefinitionVersion());
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
assertEquals(CHUNK_DATA, chunk.getData());
runInTransaction(() -> assertEquals(WorkChunkStatusEnum.IN_PROGRESS, freshFetchWorkChunk(id).getStatus()));
}
/**
* Should match the diagram in batch2_states.md
* @see hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_batch/batch2_states.md
*/
@Nested
class StateTransitions {
private String myInstanceId;
private String myChunkId;
@BeforeEach
void setUp() {
JobInstance jobInstance = createInstance();
myInstanceId = mySvc.storeNewInstance(jobInstance);
}
private String createChunk() {
return storeWorkChunk(JOB_DEFINITION_ID, TARGET_STEP_ID, myInstanceId, 0, CHUNK_DATA);
}
@Test
public void chunkCreation_isQueued() {
myChunkId = createChunk();
WorkChunk fetchedWorkChunk = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.QUEUED, fetchedWorkChunk.getStatus(), "New chunks are QUEUED");
}
@Test
public void chunkReceived_queuedToInProgress() {
myChunkId = createChunk();
// the worker has received the chunk, and marks it started.
WorkChunk chunk = mySvc.onWorkChunkDequeue(myChunkId).orElseThrow(IllegalArgumentException::new);
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
assertEquals(CHUNK_DATA, chunk.getData());
// verify the db was updated too
WorkChunk fetchedWorkChunk = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, fetchedWorkChunk.getStatus());
}
@Nested
class InProgressActions {
@BeforeEach
void setUp() {
// setup - the worker has received the chunk, and has marked it IN_PROGRESS.
myChunkId = createChunk();
mySvc.onWorkChunkDequeue(myChunkId);
}
@Test
public void processingOk_inProgressToSuccess_clearsDataSavesRecordCount() {
// execution ok
mySvc.onWorkChunkCompletion(new WorkChunkCompletionEvent(myChunkId, 3, 0));
// verify the db was updated
var workChunkEntity = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.COMPLETED, workChunkEntity.getStatus());
assertNull(workChunkEntity.getData());
assertEquals(3, workChunkEntity.getRecordsProcessed());
assertNull(workChunkEntity.getErrorMessage());
assertEquals(0, workChunkEntity.getErrorCount());
}
@Test
public void processingRetryableError_inProgressToError_bumpsCountRecordsMessage() {
// execution had a retryable error
mySvc.onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_A));
// verify the db was updated
var workChunkEntity = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, workChunkEntity.getStatus());
assertEquals(ERROR_MESSAGE_A, workChunkEntity.getErrorMessage());
assertEquals(1, workChunkEntity.getErrorCount());
}
@Test
public void processingFailure_inProgressToFailed() {
// execution had a failure
mySvc.onWorkChunkFailed(myChunkId, "some error");
// verify the db was updated
var workChunkEntity = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.FAILED, workChunkEntity.getStatus());
assertEquals("some error", workChunkEntity.getErrorMessage());
}
}
@Nested
class ErrorActions {
public static final String FIRST_ERROR_MESSAGE = ERROR_MESSAGE_A;
@BeforeEach
void setUp() {
// setup - the worker has received the chunk, and has marked it IN_PROGRESS.
myChunkId = createChunk();
mySvc.onWorkChunkDequeue(myChunkId);
// execution had a retryable error
mySvc.onWorkChunkError(new WorkChunkErrorEvent(myChunkId, FIRST_ERROR_MESSAGE));
}
/**
* The consumer will retry after a retryable error is thrown
*/
@Test
void errorRetry_errorToInProgress() {
// when consumer restarts chunk
WorkChunk chunk = mySvc.onWorkChunkDequeue(myChunkId).orElseThrow(IllegalArgumentException::new);
// then
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
// verify the db state, error message, and error count
var workChunkEntity = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, workChunkEntity.getStatus());
assertEquals(FIRST_ERROR_MESSAGE, workChunkEntity.getErrorMessage(), "Original error message kept");
assertEquals(1, workChunkEntity.getErrorCount(), "error count kept");
}
@Test
void errorRetry_repeatError_increasesErrorCount() {
// setup - the consumer is re-trying, and marks it IN_PROGRESS
mySvc.onWorkChunkDequeue(myChunkId);
// when another error happens
mySvc.onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_B));
// verify the state, new message, and error count
var workChunkEntity = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, workChunkEntity.getStatus());
assertEquals(ERROR_MESSAGE_B, workChunkEntity.getErrorMessage(), "new error message");
assertEquals(2, workChunkEntity.getErrorCount(), "error count inc");
}
@Test
void errorThenRetryAndComplete_addsErrorCounts() {
// setup - the consumer is re-trying, and marks it IN_PROGRESS
mySvc.onWorkChunkDequeue(myChunkId);
// then it completes ok.
mySvc.onWorkChunkCompletion(new WorkChunkCompletionEvent(myChunkId, 3, 1));
// verify the state, new message, and error count
var workChunkEntity = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.COMPLETED, workChunkEntity.getStatus());
assertEquals(FIRST_ERROR_MESSAGE, workChunkEntity.getErrorMessage(), "Error message kept.");
assertEquals(2, workChunkEntity.getErrorCount(), "error combined with earlier error");
}
@Test
void errorRetry_maxErrors_movesToFailed() {
// we start with 1 error already
// 2nd try
mySvc.onWorkChunkDequeue(myChunkId);
mySvc.onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_B));
var chunk = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, chunk.getStatus());
assertEquals(2, chunk.getErrorCount());
// 3rd try
mySvc.onWorkChunkDequeue(myChunkId);
mySvc.onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_B));
chunk = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, chunk.getStatus());
assertEquals(3, chunk.getErrorCount());
// 4th try
mySvc.onWorkChunkDequeue(myChunkId);
mySvc.onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_C));
chunk = freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.FAILED, chunk.getStatus());
assertEquals(4, chunk.getErrorCount());
assertThat("Error message contains last error", chunk.getErrorMessage(), containsString(ERROR_MESSAGE_C));
assertThat("Error message contains error count and complaint", chunk.getErrorMessage(), containsString("many errors: 4"));
}
}
}
@Test
public void testMarkChunkAsCompleted_Success() {
JobInstance instance = createInstance();
String instanceId = mySvc.storeNewInstance(instance);
String chunkId = storeWorkChunk(DEF_CHUNK_ID, STEP_CHUNK_ID, instanceId, SEQUENCE_NUMBER, CHUNK_DATA);
assertNotNull(chunkId);
runInTransaction(() -> assertEquals(WorkChunkStatusEnum.QUEUED, freshFetchWorkChunk(chunkId).getStatus()));
sleepUntilTimeChanges();
WorkChunk chunk = mySvc.onWorkChunkDequeue(chunkId).orElseThrow(IllegalArgumentException::new);
assertEquals(SEQUENCE_NUMBER, chunk.getSequence());
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
assertNotNull(chunk.getCreateTime());
assertNotNull(chunk.getStartTime());
assertNull(chunk.getEndTime());
assertNull(chunk.getRecordsProcessed());
assertNotNull(chunk.getData());
runInTransaction(() -> assertEquals(WorkChunkStatusEnum.IN_PROGRESS, freshFetchWorkChunk(chunkId).getStatus()));
sleepUntilTimeChanges();
runInTransaction(() -> mySvc.onWorkChunkCompletion(new WorkChunkCompletionEvent(chunkId, 50, 0)));
WorkChunk entity = freshFetchWorkChunk(chunkId);
assertEquals(WorkChunkStatusEnum.COMPLETED, entity.getStatus());
assertEquals(50, entity.getRecordsProcessed());
assertNotNull(entity.getCreateTime());
assertNotNull(entity.getStartTime());
assertNotNull(entity.getEndTime());
assertNull(entity.getData());
assertTrue(entity.getCreateTime().getTime() < entity.getStartTime().getTime());
assertTrue(entity.getStartTime().getTime() < entity.getEndTime().getTime());
}
@Test
public void testMarkChunkAsCompleted_Error() {
JobInstance instance = createInstance();
String instanceId = mySvc.storeNewInstance(instance);
String chunkId = storeWorkChunk(DEF_CHUNK_ID, STEP_CHUNK_ID, instanceId, SEQUENCE_NUMBER, null);
assertNotNull(chunkId);
runInTransaction(() -> assertEquals(WorkChunkStatusEnum.QUEUED, freshFetchWorkChunk(chunkId).getStatus()));
sleepUntilTimeChanges();
WorkChunk chunk = mySvc.onWorkChunkDequeue(chunkId).orElseThrow(IllegalArgumentException::new);
assertEquals(SEQUENCE_NUMBER, chunk.getSequence());
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
sleepUntilTimeChanges();
WorkChunkErrorEvent request = new WorkChunkErrorEvent(chunkId, ERROR_MESSAGE_A);
mySvc.onWorkChunkError(request);
runInTransaction(() -> {
WorkChunk entity = freshFetchWorkChunk(chunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, entity.getStatus());
assertEquals(ERROR_MESSAGE_A, entity.getErrorMessage());
assertNotNull(entity.getCreateTime());
assertNotNull(entity.getStartTime());
assertNotNull(entity.getEndTime());
assertEquals(1, entity.getErrorCount());
assertTrue(entity.getCreateTime().getTime() < entity.getStartTime().getTime());
assertTrue(entity.getStartTime().getTime() < entity.getEndTime().getTime());
});
// Mark errored again
WorkChunkErrorEvent request2 = new WorkChunkErrorEvent(chunkId, "This is an error message 2");
mySvc.onWorkChunkError(request2);
runInTransaction(() -> {
WorkChunk entity = freshFetchWorkChunk(chunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, entity.getStatus());
assertEquals("This is an error message 2", entity.getErrorMessage());
assertNotNull(entity.getCreateTime());
assertNotNull(entity.getStartTime());
assertNotNull(entity.getEndTime());
assertEquals(2, entity.getErrorCount());
assertTrue(entity.getCreateTime().getTime() < entity.getStartTime().getTime());
assertTrue(entity.getStartTime().getTime() < entity.getEndTime().getTime());
});
List<WorkChunk> chunks = ImmutableList.copyOf(mySvc.fetchAllWorkChunksIterator(instanceId, true));
assertEquals(1, chunks.size());
assertEquals(2, chunks.get(0).getErrorCount());
}
@Test
public void testMarkChunkAsCompleted_Fail() {
JobInstance instance = createInstance();
String instanceId = mySvc.storeNewInstance(instance);
String chunkId = storeWorkChunk(DEF_CHUNK_ID, STEP_CHUNK_ID, instanceId, SEQUENCE_NUMBER, null);
assertNotNull(chunkId);
runInTransaction(() -> assertEquals(WorkChunkStatusEnum.QUEUED, freshFetchWorkChunk(chunkId).getStatus()));
sleepUntilTimeChanges();
WorkChunk chunk = mySvc.onWorkChunkDequeue(chunkId).orElseThrow(IllegalArgumentException::new);
assertEquals(SEQUENCE_NUMBER, chunk.getSequence());
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
sleepUntilTimeChanges();
mySvc.onWorkChunkFailed(chunkId, "This is an error message");
runInTransaction(() -> {
WorkChunk entity = freshFetchWorkChunk(chunkId);
assertEquals(WorkChunkStatusEnum.FAILED, entity.getStatus());
assertEquals("This is an error message", entity.getErrorMessage());
assertNotNull(entity.getCreateTime());
assertNotNull(entity.getStartTime());
assertNotNull(entity.getEndTime());
assertTrue(entity.getCreateTime().getTime() < entity.getStartTime().getTime());
assertTrue(entity.getStartTime().getTime() < entity.getEndTime().getTime());
});
}
@Test
public void markWorkChunksWithStatusAndWipeData_marksMultipleChunksWithStatus_asExpected() {
JobInstance instance = createInstance();
String instanceId = mySvc.storeNewInstance(instance);
ArrayList<String> chunkIds = new ArrayList<>();
for (int i = 0; i < 10; i++) {
WorkChunkCreateEvent chunk = new WorkChunkCreateEvent(
"defId",
1,
"stepId",
instanceId,
0,
"{}"
);
String id = mySvc.onWorkChunkCreate(chunk);
chunkIds.add(id);
}
runInTransaction(() -> mySvc.markWorkChunksWithStatusAndWipeData(instance.getInstanceId(), chunkIds, WorkChunkStatusEnum.COMPLETED, null));
Iterator<WorkChunk> reducedChunks = mySvc.fetchAllWorkChunksIterator(instanceId, true);
while (reducedChunks.hasNext()) {
WorkChunk reducedChunk = reducedChunks.next();
assertTrue(chunkIds.contains(reducedChunk.getId()));
assertEquals(WorkChunkStatusEnum.COMPLETED, reducedChunk.getStatus());
}
}
public PlatformTransactionManager getTransactionManager() {
return myTransactionManager;
}
/**
* Test
* * @see hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_batch/batch2_states.md
*/
@Nested
class InstanceStateTransitions {
@Test
void createInstance_createsInQueuedWithChunk() {
// given
JobDefinition<?> jd = withJobDefinition();
// when
IJobPersistence.CreateResult createResult =
newTxTemplate().execute(status->
mySvc.onCreateWithFirstChunk(jd, "{}"));
// then
ourLog.info("job and chunk created {}", createResult);
assertNotNull(createResult);
assertThat(createResult.jobInstanceId, not(emptyString()));
assertThat(createResult.workChunkId, not(emptyString()));
JobInstance jobInstance = freshFetchJobInstance(createResult.jobInstanceId);
assertThat(jobInstance.getStatus(), equalTo(StatusEnum.QUEUED));
assertThat(jobInstance.getParameters(), equalTo("{}"));
WorkChunk firstChunk = freshFetchWorkChunk(createResult.workChunkId);
assertThat(firstChunk.getStatus(), equalTo(WorkChunkStatusEnum.QUEUED));
assertNull(firstChunk.getData(), "First chunk data is null - only uses parameters");
}
@Test
void testCreateInstance_firstChunkDequeued_movesToInProgress() {
// given
JobDefinition<?> jd = withJobDefinition();
IJobPersistence.CreateResult createResult = newTxTemplate().execute(status->
mySvc.onCreateWithFirstChunk(jd, "{}"));
assertNotNull(createResult);
// when
newTxTemplate().execute(status -> mySvc.onChunkDequeued(createResult.jobInstanceId));
// then
JobInstance jobInstance = freshFetchJobInstance(createResult.jobInstanceId);
assertThat(jobInstance.getStatus(), equalTo(StatusEnum.IN_PROGRESS));
}
@ParameterizedTest
@EnumSource(StatusEnum.class)
void cancelRequest_cancelsJob_whenNotFinalState(StatusEnum theState) {
// given
JobInstance cancelledInstance = createInstance();
cancelledInstance.setStatus(theState);
String instanceId1 = mySvc.storeNewInstance(cancelledInstance);
mySvc.cancelInstance(instanceId1);
JobInstance normalInstance = createInstance();
normalInstance.setStatus(theState);
String instanceId2 = mySvc.storeNewInstance(normalInstance);
JobDefinitionRegistry jobDefinitionRegistry = new JobDefinitionRegistry();
jobDefinitionRegistry.addJobDefinitionIfNotRegistered(withJobDefinition());
// when
runInTransaction(()-> new JobInstanceProcessor(mySvc, null, instanceId1, new JobChunkProgressAccumulator(), null, jobDefinitionRegistry)
.process());
// then
JobInstance freshInstance1 = mySvc.fetchInstance(instanceId1).orElseThrow();
if (theState.isCancellable()) {
assertEquals(StatusEnum.CANCELLED, freshInstance1.getStatus(), "cancel request processed");
assertThat(freshInstance1.getErrorMessage(), containsString("Job instance cancelled"));
} else {
assertEquals(theState, freshInstance1.getStatus(), "cancel request ignored - state unchanged");
assertNull(freshInstance1.getErrorMessage(), "no error message");
}
JobInstance freshInstance2 = mySvc.fetchInstance(instanceId2).orElseThrow();
assertEquals(theState, freshInstance2.getStatus(), "cancel request ignored - cancelled not set");
}
public IJobPersistence getSvc() {
return mySvc;
}
@Test
void testDeleteChunksAndMarkInstanceAsChunksPurged_doesWhatItSays() {
// given
JobDefinition<?> jd = withJobDefinition();
IJobPersistence.CreateResult createResult = newTxTemplate().execute(status->
mySvc.onCreateWithFirstChunk(jd, "{}"));
String instanceId = createResult.jobInstanceId;
for (int i = 0; i < 10; i++) {
storeWorkChunk(JOB_DEFINITION_ID, TARGET_STEP_ID, instanceId, i, CHUNK_DATA);
}
JobInstance readback = freshFetchJobInstance(instanceId);
assertFalse(readback.isWorkChunksPurged());
assertTrue(mySvc.fetchAllWorkChunksIterator(instanceId, true).hasNext(), "has chunk");
// when
mySvc.deleteChunksAndMarkInstanceAsChunksPurged(instanceId);
// then
readback = freshFetchJobInstance(instanceId);
assertTrue(readback.isWorkChunksPurged(), "purged set");
assertFalse(mySvc.fetchAllWorkChunksIterator(instanceId, true).hasNext(), "chunks gone");
}
@Test
void testInstanceUpdate_modifierApplied() {
// given
String instanceId = mySvc.storeNewInstance(createInstance());
// when
mySvc.updateInstance(instanceId, instance ->{
instance.setErrorCount(42);
return true;
});
// then
JobInstance jobInstance = freshFetchJobInstance(instanceId);
assertEquals(42, jobInstance.getErrorCount());
}
@Test
void testInstanceUpdate_modifierNotAppliedWhenPredicateReturnsFalse() {
// given
JobInstance instance1 = createInstance();
boolean initialValue = true;
instance1.setFastTracking(initialValue);
String instanceId = mySvc.storeNewInstance(instance1);
// when
mySvc.updateInstance(instanceId, instance ->{
instance.setFastTracking(false);
return false;
});
// then
JobInstance jobInstance = freshFetchJobInstance(instanceId);
assertEquals(initialValue, jobInstance.isFastTracking());
}
private JobDefinition<TestJobParameters> withJobDefinition() {
return JobDefinition.newBuilder()
.setJobDefinitionId(JOB_DEFINITION_ID)
public JobDefinition<TestJobParameters> withJobDefinition(boolean theIsGatedBoolean) {
JobDefinition.Builder<TestJobParameters, ?> builder = JobDefinition.newBuilder()
.setJobDefinitionId(theIsGatedBoolean ? GATED_JOB_DEFINITION_ID : JOB_DEFINITION_ID)
.setJobDefinitionVersion(JOB_DEF_VER)
.setJobDescription("A job description")
.setParametersType(TestJobParameters.class)
.addFirstStep(TARGET_STEP_ID, "the first step", TestJobStep2InputType.class, (theStepExecutionDetails, theDataSink) -> new RunOutcome(0))
.addIntermediateStep("2nd-step-id", "the second step", TestJobStep3InputType.class, (theStepExecutionDetails, theDataSink) -> new RunOutcome(0))
.addLastStep("last-step-id", "the final step", (theStepExecutionDetails, theDataSink) -> new RunOutcome(0))
.build();
.addLastStep("last-step-id", "the final step", (theStepExecutionDetails, theDataSink) -> new RunOutcome(0));
if (theIsGatedBoolean) {
builder.gatedExecution();
}
return builder.build();
}
@AfterEach
public void after() {
myJobDefinitionRegistry.removeJobDefinition(JOB_DEFINITION_ID, JOB_DEF_VER);
// clear invocations on the batch sender from previous jobs that might be
// kicking around
Mockito.clearInvocations(myBatchJobSender);
}
@Override
public ITestFixture getTestManager() {
return this;
}
@Override
public void enableMaintenanceRunner(boolean theToEnable) {
myMaintenanceService.enableMaintenancePass(theToEnable);
}
@Nested
class WorkChunkStorage implements IWorkChunkStorageTests {
@Override
public ITestFixture getTestManager() {
return AbstractIJobPersistenceSpecificationTest.this;
}
@Nested
class StateTransitions implements IWorkChunkStateTransitions {
@Override
public ITestFixture getTestManager() {
return AbstractIJobPersistenceSpecificationTest.this;
}
@Nested
class ErrorActions implements IWorkChunkErrorActionsTests {
@Override
public ITestFixture getTestManager() {
return AbstractIJobPersistenceSpecificationTest.this;
}
}
}
}
@Nonnull
private JobInstance createInstance() {
public JobInstance createInstance(JobDefinition<?> theJobDefinition) {
JobDefinition<?> jobDefinition = theJobDefinition == null ? withJobDefinition(false)
: theJobDefinition;
if (myJobDefinitionRegistry.getJobDefinition(jobDefinition.getJobDefinitionId(), jobDefinition.getJobDefinitionVersion()).isEmpty()) {
myJobDefinitionRegistry.addJobDefinition(jobDefinition);
}
JobInstance instance = new JobInstance();
instance.setJobDefinitionId(JOB_DEFINITION_ID);
instance.setJobDefinitionId(jobDefinition.getJobDefinitionId());
instance.setJobDefinitionVersion(jobDefinition.getJobDefinitionVersion());
instance.setStatus(StatusEnum.QUEUED);
instance.setJobDefinitionVersion(JOB_DEF_VER);
instance.setParameters(CHUNK_DATA);
@ -634,18 +170,20 @@ public abstract class AbstractIJobPersistenceSpecificationTest {
return instance;
}
private String storeWorkChunk(String theJobDefinitionId, String theTargetStepId, String theInstanceId, int theSequence, String theSerializedData) {
public String storeWorkChunk(String theJobDefinitionId, String theTargetStepId, String theInstanceId, int theSequence, String theSerializedData) {
WorkChunkCreateEvent batchWorkChunk = new WorkChunkCreateEvent(theJobDefinitionId, JOB_DEF_VER, theTargetStepId, theInstanceId, theSequence, theSerializedData);
return mySvc.onWorkChunkCreate(batchWorkChunk);
}
public abstract PlatformTransactionManager getTxManager();
protected abstract PlatformTransactionManager getTxManager();
protected abstract WorkChunk freshFetchWorkChunk(String theChunkId);
protected JobInstance freshFetchJobInstance(String theInstanceId) {
public JobInstance freshFetchJobInstance(String theInstanceId) {
return runInTransaction(() -> mySvc.fetchInstance(theInstanceId).orElseThrow());
}
@Override
public abstract void runMaintenancePass();
public TransactionTemplate newTxTemplate() {
TransactionTemplate retVal = new TransactionTemplate(getTxManager());
retVal.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
@ -681,6 +219,39 @@ public abstract class AbstractIJobPersistenceSpecificationTest {
await().until(() -> sw.getMillis() > 0);
}
public String createAndStoreJobInstance(JobDefinition<?> theJobDefinition) {
JobInstance jobInstance = createInstance(theJobDefinition);
return mySvc.storeNewInstance(jobInstance);
}
public String createAndDequeueWorkChunk(String theJobInstanceId) {
String chunkId = createChunk(theJobInstanceId);
mySvc.onWorkChunkDequeue(chunkId);
return chunkId;
}
@Override
public abstract WorkChunk freshFetchWorkChunk(String theChunkId);
public String createChunk(String theInstanceId) {
return storeWorkChunk(JOB_DEFINITION_ID, TARGET_STEP_ID, theInstanceId, 0, CHUNK_DATA);
}
public PointcutLatch disableWorkChunkMessageHandler() {
PointcutLatch latch = new PointcutLatch(new Exception().getStackTrace()[0].getMethodName());
doAnswer(a -> {
latch.call(1);
return Void.class;
}).when(myBatchJobSender).sendWorkChannelMessage(any(JobWorkNotification.class));
return latch;
}
public void verifyWorkChunkMessageHandlerCalled(PointcutLatch theSendingLatch, int theNumberOfTimes) throws InterruptedException {
theSendingLatch.awaitExpected();
ArgumentCaptor<JobWorkNotification> notificationCaptor = ArgumentCaptor.forClass(JobWorkNotification.class);
verify(myBatchJobSender, times(theNumberOfTimes))
.sendWorkChannelMessage(notificationCaptor.capture());
}
}

View File

@ -0,0 +1,55 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.model.WorkChunkCompletionEvent;
import ca.uhn.fhir.batch2.model.WorkChunkErrorEvent;
import ca.uhn.fhir.batch2.model.WorkChunkStatusEnum;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public interface IInProgressActionsTests extends IWorkChunkCommon, WorkChunkTestConstants {
@Test
default void processingOk_inProgressToSuccess_clearsDataSavesRecordCount() {
String jobId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createAndDequeueWorkChunk(jobId);
// execution ok
getTestManager().getSvc().onWorkChunkCompletion(new WorkChunkCompletionEvent(myChunkId, 3, 0));
// verify the db was updated
var workChunkEntity = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.COMPLETED, workChunkEntity.getStatus());
assertNull(workChunkEntity.getData());
assertEquals(3, workChunkEntity.getRecordsProcessed());
assertNull(workChunkEntity.getErrorMessage());
assertEquals(0, workChunkEntity.getErrorCount());
}
@Test
default void processingRetryableError_inProgressToError_bumpsCountRecordsMessage() {
String jobId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createAndDequeueWorkChunk(jobId);
// execution had a retryable error
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_A));
// verify the db was updated
var workChunkEntity = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, workChunkEntity.getStatus());
assertEquals(ERROR_MESSAGE_A, workChunkEntity.getErrorMessage());
assertEquals(1, workChunkEntity.getErrorCount());
}
@Test
default void processingFailure_inProgressToFailed() {
String jobId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createAndDequeueWorkChunk(jobId);
// execution had a failure
getTestManager().getSvc().onWorkChunkFailed(myChunkId, "some error");
// verify the db was updated
var workChunkEntity = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.FAILED, workChunkEntity.getStatus());
assertEquals("some error", workChunkEntity.getErrorMessage());
}
}

View File

@ -0,0 +1,82 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.api.IJobPersistence;
import ca.uhn.fhir.batch2.coordinator.JobDefinitionRegistry;
import ca.uhn.fhir.batch2.maintenance.JobChunkProgressAccumulator;
import ca.uhn.fhir.batch2.maintenance.JobInstanceProcessor;
import ca.uhn.fhir.batch2.model.JobDefinition;
import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.batch2.model.StatusEnum;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public interface IInstanceStateTransitions extends IWorkChunkCommon, WorkChunkTestConstants {
Logger ourLog = LoggerFactory.getLogger(IInstanceStateTransitions.class);
@Test
default void testCreateInstance_firstChunkDequeued_movesToInProgress() {
// given
JobDefinition<?> jd = getTestManager().withJobDefinition(false);
IJobPersistence.CreateResult createResult = getTestManager().newTxTemplate().execute(status->
getTestManager().getSvc().onCreateWithFirstChunk(jd, "{}"));
assertNotNull(createResult);
// when
getTestManager().newTxTemplate().execute(status -> getTestManager().getSvc().onChunkDequeued(createResult.jobInstanceId));
// then
JobInstance jobInstance = getTestManager().freshFetchJobInstance(createResult.jobInstanceId);
assertThat(jobInstance.getStatus(), equalTo(StatusEnum.IN_PROGRESS));
}
@ParameterizedTest
@EnumSource(StatusEnum.class)
default void cancelRequest_cancelsJob_whenNotFinalState(StatusEnum theState) {
// given
JobInstance cancelledInstance = createInstance();
cancelledInstance.setStatus(theState);
String instanceId1 = getTestManager().getSvc().storeNewInstance(cancelledInstance);
getTestManager().getSvc().cancelInstance(instanceId1);
JobInstance normalInstance = createInstance();
normalInstance.setStatus(theState);
String instanceId2 = getTestManager().getSvc().storeNewInstance(normalInstance);
JobDefinitionRegistry jobDefinitionRegistry = new JobDefinitionRegistry();
jobDefinitionRegistry.addJobDefinitionIfNotRegistered(getTestManager().withJobDefinition(false));
// when
getTestManager().runInTransaction(()-> {
new JobInstanceProcessor(
getTestManager().getSvc(),
null,
instanceId1,
new JobChunkProgressAccumulator(),
null,
jobDefinitionRegistry
).process();
});
// then
JobInstance freshInstance1 = getTestManager().getSvc().fetchInstance(instanceId1).orElseThrow();
if (theState.isCancellable()) {
assertEquals(StatusEnum.CANCELLED, freshInstance1.getStatus(), "cancel request processed");
assertThat(freshInstance1.getErrorMessage(), containsString("Job instance cancelled"));
} else {
assertEquals(theState, freshInstance1.getStatus(), "cancel request ignored - state unchanged");
assertNull(freshInstance1.getErrorMessage(), "no error message");
}
JobInstance freshInstance2 = getTestManager().getSvc().fetchInstance(instanceId2).orElseThrow();
assertEquals(theState, freshInstance2.getStatus(), "cancel request ignored - cancelled not set");
}
}

View File

@ -0,0 +1,65 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.api.IJobPersistence;
import ca.uhn.fhir.batch2.model.JobDefinition;
import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.batch2.model.WorkChunk;
import ca.uhn.hapi.fhir.batch2.test.support.TestJobParameters;
import ca.uhn.test.concurrency.PointcutLatch;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
public interface ITestFixture {
String createAndStoreJobInstance(JobDefinition<?> theJobDefinition);
String createAndDequeueWorkChunk(String theJobInstanceId);
WorkChunk freshFetchWorkChunk(String theChunkId);
String storeWorkChunk(String theJobDefinitionId, String theTargetStepId, String theInstanceId, int theSequence, String theSerializedData);
void runInTransaction(Runnable theRunnable);
void sleepUntilTimeChanges();
JobDefinition<TestJobParameters> withJobDefinition(boolean theIsGatedJob);
TransactionTemplate newTxTemplate();
JobInstance freshFetchJobInstance(String theInstanceId);
void runMaintenancePass();
PlatformTransactionManager getTransactionManager();
IJobPersistence getSvc();
/**
* This assumes a creation of JOB_DEFINITION already
* @param theJobInstanceId
* @return
*/
String createChunk(String theJobInstanceId);
/**
* Enable/disable the maintenance runner (So it doesn't run on a scheduler)
*/
void enableMaintenanceRunner(boolean theToEnable);
/**
* Disables the workchunk message handler
* so that we do not actually send messages to the queue;
* useful if mocking state transitions and we don't want to test
* dequeuing.
* Returns a latch that will fire each time a message is sent.
*/
PointcutLatch disableWorkChunkMessageHandler();
/**
*
* @param theSendingLatch the latch sent back from the disableWorkChunkMessageHandler method above
* @param theNumberOfTimes the number of invocations to expect
*/
void verifyWorkChunkMessageHandlerCalled(PointcutLatch theSendingLatch, int theNumberOfTimes) throws InterruptedException;
}

View File

@ -0,0 +1,23 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.batch2.model.StatusEnum;
public interface IWorkChunkCommon extends WorkChunkTestConstants {
/**
* Returns the concrete class that is implementing this stuff.
* Used primarily for structure
*/
ITestFixture getTestManager();
default JobInstance createInstance() {
JobInstance instance = new JobInstance();
instance.setJobDefinitionId(JOB_DEFINITION_ID);
instance.setStatus(StatusEnum.QUEUED);
instance.setJobDefinitionVersion(JOB_DEF_VER);
instance.setParameters(CHUNK_DATA);
instance.setReport("TEST");
return instance;
}
}

View File

@ -0,0 +1,108 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.model.WorkChunk;
import ca.uhn.fhir.batch2.model.WorkChunkCompletionEvent;
import ca.uhn.fhir.batch2.model.WorkChunkErrorEvent;
import ca.uhn.fhir.batch2.model.WorkChunkStatusEnum;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
public interface IWorkChunkErrorActionsTests extends IWorkChunkCommon, WorkChunkTestConstants {
/**
* The consumer will retry after a retryable error is thrown
*/
@Test
default void errorRetry_errorToInProgress() {
String jobId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createAndDequeueWorkChunk(jobId);
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, FIRST_ERROR_MESSAGE));
// when consumer restarts chunk
WorkChunk chunk = getTestManager().getSvc().onWorkChunkDequeue(myChunkId).orElseThrow(IllegalArgumentException::new);
// then
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
// verify the db state, error message, and error count
var workChunkEntity = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, workChunkEntity.getStatus());
assertEquals(FIRST_ERROR_MESSAGE, workChunkEntity.getErrorMessage(), "Original error message kept");
assertEquals(1, workChunkEntity.getErrorCount(), "error count kept");
}
@Test
default void errorRetry_repeatError_increasesErrorCount() {
String jobId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createAndDequeueWorkChunk(jobId);
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, FIRST_ERROR_MESSAGE));
// setup - the consumer is re-trying, and marks it IN_PROGRESS
getTestManager().getSvc().onWorkChunkDequeue(myChunkId);
// when another error happens
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_B));
// verify the state, new message, and error count
var workChunkEntity = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, workChunkEntity.getStatus());
assertEquals(ERROR_MESSAGE_B, workChunkEntity.getErrorMessage(), "new error message");
assertEquals(2, workChunkEntity.getErrorCount(), "error count inc");
}
@Test
default void errorThenRetryAndComplete_addsErrorCounts() {
String jobId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createAndDequeueWorkChunk(jobId);
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, FIRST_ERROR_MESSAGE));
// setup - the consumer is re-trying, and marks it IN_PROGRESS
getTestManager().getSvc().onWorkChunkDequeue(myChunkId);
// then it completes ok.
getTestManager().getSvc().onWorkChunkCompletion(new WorkChunkCompletionEvent(myChunkId, 3, 1));
// verify the state, new message, and error count
var workChunkEntity = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.COMPLETED, workChunkEntity.getStatus());
assertEquals(FIRST_ERROR_MESSAGE, workChunkEntity.getErrorMessage(), "Error message kept.");
assertEquals(2, workChunkEntity.getErrorCount(), "error combined with earlier error");
}
@Test
default void errorRetry_maxErrors_movesToFailed() {
// we start with 1 error already
String jobId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createAndDequeueWorkChunk(jobId);
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, FIRST_ERROR_MESSAGE));
// 2nd try
getTestManager().getSvc().onWorkChunkDequeue(myChunkId);
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_B));
var chunk = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, chunk.getStatus());
assertEquals(2, chunk.getErrorCount());
// 3rd try
getTestManager().getSvc().onWorkChunkDequeue(myChunkId);
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_B));
chunk = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.ERRORED, chunk.getStatus());
assertEquals(3, chunk.getErrorCount());
// 4th try
getTestManager().getSvc().onWorkChunkDequeue(myChunkId);
getTestManager().getSvc().onWorkChunkError(new WorkChunkErrorEvent(myChunkId, ERROR_MESSAGE_C));
chunk = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.FAILED, chunk.getStatus());
assertEquals(4, chunk.getErrorCount());
assertThat("Error message contains last error", chunk.getErrorMessage(), containsString(ERROR_MESSAGE_C));
assertThat("Error message contains error count and complaint", chunk.getErrorMessage(), containsString("many errors: 4"));
}
}

View File

@ -0,0 +1,32 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.model.WorkChunk;
import ca.uhn.fhir.batch2.model.WorkChunkStatusEnum;
import ca.uhn.test.concurrency.PointcutLatch;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
public interface IWorkChunkStateTransitions extends IWorkChunkCommon, WorkChunkTestConstants {
Logger ourLog = LoggerFactory.getLogger(IWorkChunkStateTransitions.class);
@Test
default void chunkReceived_queuedToInProgress() throws InterruptedException {
String jobInstanceId = getTestManager().createAndStoreJobInstance(null);
String myChunkId = getTestManager().createChunk(jobInstanceId);
getTestManager().runMaintenancePass();
// the worker has received the chunk, and marks it started.
WorkChunk chunk = getTestManager().getSvc().onWorkChunkDequeue(myChunkId).orElseThrow(IllegalArgumentException::new);
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, chunk.getStatus());
assertEquals(CHUNK_DATA, chunk.getData());
// verify the db was updated too
WorkChunk fetchedWorkChunk = getTestManager().freshFetchWorkChunk(myChunkId);
assertEquals(WorkChunkStatusEnum.IN_PROGRESS, fetchedWorkChunk.getStatus());
}
}

View File

@ -0,0 +1,23 @@
package ca.uhn.hapi.fhir.batch2.test;
import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.batch2.model.WorkChunk;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNull;
public interface IWorkChunkStorageTests extends IWorkChunkCommon, WorkChunkTestConstants {
@Test
default void testStoreAndFetchWorkChunk_NoData() {
JobInstance instance = createInstance();
String instanceId = getTestManager().getSvc().storeNewInstance(instance);
String id = getTestManager().storeWorkChunk(JOB_DEFINITION_ID, TARGET_STEP_ID, instanceId, 0, null);
getTestManager().runInTransaction(() -> {
WorkChunk chunk = getTestManager().freshFetchWorkChunk(id);
assertNull(chunk.getData());
});
}
}

View File

@ -0,0 +1,18 @@
package ca.uhn.hapi.fhir.batch2.test;
public interface WorkChunkTestConstants {
public static final String JOB_DEFINITION_ID = "definition-id";
// we use a separate id for gated jobs because these job definitions might not
// be cleaned up after any given test run
String GATED_JOB_DEFINITION_ID = "gated_job_def_id";
public static final String TARGET_STEP_ID = "step-id";
public static final String DEF_CHUNK_ID = "definition-chunkId";
public static final int JOB_DEF_VER = 1;
public static final int SEQUENCE_NUMBER = 1;
public static final String CHUNK_DATA = "{\"key\":\"value\"}";
public static final String ERROR_MESSAGE_A = "This is an error message: A";
public static final String ERROR_MESSAGE_B = "This is a different error message: B";
public static final String ERROR_MESSAGE_C = "This is a different error message: C";
String FIRST_ERROR_MESSAGE = ERROR_MESSAGE_A;
}

View File

@ -0,0 +1,27 @@
package ca.uhn.hapi.fhir.batch2.test.configs;
import ca.uhn.fhir.batch2.channel.BatchJobSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import static org.mockito.Mockito.spy;
/**
* Provides spying overrides of beans we want to spy on.
*
* We spy the BatchJobSender so we can test state transitions without
* actually sending messages onto the queue
*/
@Configuration
public class SpyOverrideConfig {
@Autowired
BatchJobSender myRealJobSender;
@Primary
@Bean
public BatchJobSender batchJobSenderSpy() {
return spy(myRealJobSender);
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -36,4 +36,11 @@ public interface IJobMaintenanceService {
*/
@VisibleForTesting
void forceMaintenancePass();
/**
* This is only to be called in a testing environment
* to ensure state changes are controlled.
*/
@VisibleForTesting
void enableMaintenancePass(boolean thetoEnable);
}

View File

@ -97,6 +97,8 @@ public class JobMaintenanceServiceImpl implements IJobMaintenanceService, IHasSc
private Runnable myMaintenanceJobFinishedCallback = () -> {};
private final IReductionStepExecutorService myReductionStepExecutorService;
private boolean myEnabledBool = true;
/**
* Constructor
*/
@ -196,8 +198,17 @@ public class JobMaintenanceServiceImpl implements IJobMaintenanceService, IHasSc
doMaintenancePass();
}
@Override
public void enableMaintenancePass(boolean theToEnable) {
myEnabledBool = theToEnable;
}
@Override
public void runMaintenancePass() {
if (!myEnabledBool) {
ourLog.error("Maintenance job is disabled! This will affect all batch2 jobs!");
}
if (!myRunMaintenanceSemaphore.tryAcquire()) {
ourLog.debug("Another maintenance pass is already in progress. Ignoring request.");
return;

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -9,7 +9,7 @@
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<packaging>pom</packaging>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<name>HAPI-FHIR</name>
<description>An open-source implementation of the FHIR specification in Java.</description>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.1.7-SNAPSHOT</version>
<version>7.1.8-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>