Improve TX scoping for validation (#1925)

* Improve TX scoping for validation

* Test fix

* Test fix

* Some cleanup

* Test fixes

* Test fix
This commit is contained in:
James Agnew 2020-06-17 14:04:12 -04:00 committed by GitHub
parent 0d4e12fe58
commit b6540064ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 253 additions and 36 deletions

View File

@ -6,9 +6,9 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>5.1.0-SNAPSHOT</version> <version>5.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<artifactId>hapi-fhir-elasticsearch-6</artifactId> <artifactId>hapi-fhir-elasticsearch-6</artifactId>

View File

@ -150,6 +150,12 @@
<artifactId>hapi-fhir-elasticsearch-6</artifactId> <artifactId>hapi-fhir-elasticsearch-6</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<classifier>shaded6</classifier> <classifier>shaded6</classifier>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>

View File

@ -1395,6 +1395,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional(propagation = Propagation.SUPPORTS)
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) { public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
if (theRequest != null) { if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId); ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);

View File

@ -412,8 +412,4 @@ public class TermConcept implements Serializable {
return getChildren().stream().map(t -> t.getChild()).collect(Collectors.toList()); return getChildren().stream().map(t -> t.getChild()).collect(Collectors.toList());
} }
public VersionIndependentConcept toVersionIndependentConcept() {
return new VersionIndependentConcept(myCodeSystem.getCodeSystem().getCodeSystemUri(), myCode);
}
} }

View File

@ -408,7 +408,7 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
.build() .build()
.execute(new HttpGet(thePackageUrl))) { .execute(new HttpGet(thePackageUrl))) {
if (request.getStatusLine().getStatusCode() != 200) { if (request.getStatusLine().getStatusCode() != 200) {
throw new IOException("Received HTTP " + request.getStatusLine().getStatusCode()); throw new ResourceNotFoundException("Received HTTP " + request.getStatusLine().getStatusCode() + " from URL: " + thePackageUrl);
} }
return IOUtils.toByteArray(request.getEntity().getContent()); return IOUtils.toByteArray(request.getEntity().getContent());
} catch (IOException e) { } catch (IOException e) {

View File

@ -89,6 +89,7 @@ import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import net.bytebuddy.implementation.bytecode.Throw;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
@ -177,6 +178,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L); private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L);
private static boolean ourLastResultsFromTranslationCache; // For testing. private static boolean ourLastResultsFromTranslationCache; // For testing.
private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing. private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing.
private static Runnable myInvokeOnNextCallForUnitTest;
private final int myFetchSize = DEFAULT_FETCH_SIZE; private final int myFetchSize = DEFAULT_FETCH_SIZE;
private final Cache<String, TermCodeSystemVersion> myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(); private final Cache<String, TermCodeSystemVersion> myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
@Autowired @Autowired
@ -1299,7 +1301,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
@Nullable @Nullable
private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) { private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) {
TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> { TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> myTxTemplate.execute(tx -> {
TermCodeSystemVersion csv = null; TermCodeSystemVersion csv = null;
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri); TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri);
if (cs != null && cs.getCurrentVersion() != null) { if (cs != null && cs.getCurrentVersion() != null) {
@ -1310,7 +1312,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} else { } else {
return NO_CURRENT_VERSION; return NO_CURRENT_VERSION;
} }
}); }));
if (retVal == NO_CURRENT_VERSION) { if (retVal == NO_CURRENT_VERSION) {
return null; return null;
} }
@ -1935,8 +1937,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
@Override @Override
@Transactional
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
if (myInvokeOnNextCallForUnitTest != null) {
Runnable invokeOnNextCallForUnitTest = myInvokeOnNextCallForUnitTest;
myInvokeOnNextCallForUnitTest = null;
invokeOnNextCallForUnitTest.run();
}
IPrimitiveType<?> urlPrimitive = myContext.newTerser().getSingleValueOrNull(theValueSet, "url", IPrimitiveType.class); IPrimitiveType<?> urlPrimitive = myContext.newTerser().getSingleValueOrNull(theValueSet, "url", IPrimitiveType.class);
String url = urlPrimitive.getValueAsString(); String url = urlPrimitive.getValueAsString();
if (isNotBlank(url)) { if (isNotBlank(url)) {
@ -2106,6 +2115,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
@VisibleForTesting
public static void setInvokeOnNextCallForUnitTest(Runnable theInvokeOnNextCallForUnitTest) {
myInvokeOnNextCallForUnitTest = theInvokeOnNextCallForUnitTest;
}
static List<TermConcept> toPersistedConcepts(List<CodeSystem.ConceptDefinitionComponent> theConcept, TermCodeSystemVersion theCodeSystemVersion) { static List<TermConcept> toPersistedConcepts(List<CodeSystem.ConceptDefinitionComponent> theConcept, TermCodeSystemVersion theCodeSystemVersion) {
ArrayList<TermConcept> retVal = new ArrayList<>(); ArrayList<TermConcept> retVal = new ArrayList<>();

View File

@ -447,16 +447,19 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
doDelete(descriptor, loader, counter, myConceptDao); doDelete(descriptor, loader, counter, myConceptDao);
} }
Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
if (codeSystemOpt.isPresent()) { txTemplate.executeWithoutResult(tx -> {
TermCodeSystem codeSystem = codeSystemOpt.get(); Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid);
ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid()); if (codeSystemOpt.isPresent()) {
codeSystem.setCurrentVersion(null); TermCodeSystem codeSystem = codeSystemOpt.get();
myCodeSystemDao.save(codeSystem); ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid());
} codeSystem.setCurrentVersion(null);
myCodeSystemDao.save(codeSystem);
}
ourLog.info(" * Deleting code system version"); ourLog.info(" * Deleting code system version");
myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid); myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid);
});
} }

View File

@ -144,7 +144,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
if (!haveValidated) { if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
} }
if (codeOpt != null && codeOpt.isPresent()) { if (codeOpt != null && codeOpt.isPresent()) {

View File

@ -112,7 +112,7 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
if (!haveValidated) { if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
} }
if (codeOpt != null && codeOpt.isPresent()) { if (codeOpt != null && codeOpt.isPresent()) {

View File

@ -99,7 +99,7 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
if (!haveValidated) { if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
} }
if (codeOpt != null && codeOpt.isPresent()) { if (codeOpt != null && codeOpt.isPresent()) {

View File

@ -41,6 +41,11 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
* starvation * starvation
*/ */
ourMaxThreads = (int) (Math.random() * 6.0) + 1; ourMaxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
ourMaxThreads = 1;
}
} }
private Exception myLastStackTrace; private Exception myLastStackTrace;

View File

@ -94,6 +94,11 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
* starvation * starvation
*/ */
int maxThreads = (int) (Math.random() * 6.0) + 1; int maxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
maxThreads = 1;
}
retVal.setMaxTotal(maxThreads); retVal.setMaxTotal(maxThreads);
return retVal; return retVal;

View File

@ -43,6 +43,10 @@ public class TestR4Config extends BaseJavaConfigR4 {
*/ */
if (ourMaxThreads == null) { if (ourMaxThreads == null) {
ourMaxThreads = (int) (Math.random() * 6.0) + 1; ourMaxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
ourMaxThreads = 1;
}
} }
} }

View File

@ -41,6 +41,11 @@ public class TestR5Config extends BaseJavaConfigR5 {
*/ */
if (ourMaxThreads == null) { if (ourMaxThreads == null) {
ourMaxThreads = (int) (Math.random() * 6.0) + 1; ourMaxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
ourMaxThreads = 1;
}
} }
} }

View File

@ -43,6 +43,7 @@ import org.hibernate.HibernateException;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -133,7 +134,8 @@ public abstract class BaseJpaTest extends BaseTest {
@Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT) @Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT)
@Autowired @Autowired
private IValidationSupport myJpaPersistedValidationSupport; private IValidationSupport myJpaPersistedValidationSupport;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
@After @After
public void afterPerformCleanup() { public void afterPerformCleanup() {
@ -150,7 +152,9 @@ public abstract class BaseJpaTest extends BaseTest {
if (myJpaPersistedValidationSupport != null) { if (myJpaPersistedValidationSupport != null) {
ProxyUtil.getSingletonTarget(myJpaPersistedValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches(); ProxyUtil.getSingletonTarget(myJpaPersistedValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches();
} }
if (myFhirInstanceValidator != null) {
myFhirInstanceValidator.invalidateCaches();
}
} }

View File

@ -145,7 +145,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
// Validate once // Validate once
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
myObservationDao.validate(obs, null, null, null, null, null, null); myObservationDao.validate(obs, null, null, null, null, null, null);
assertEquals(myCaptureQueriesListener.logSelectQueriesForCurrentThread(), 10, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logSelectQueriesForCurrentThread(), 9, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logUpdateQueriesForCurrentThread(), 0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logUpdateQueriesForCurrentThread(), 0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logInsertQueriesForCurrentThread(), 0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logInsertQueriesForCurrentThread(), 0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logDeleteQueriesForCurrentThread(), 0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logDeleteQueriesForCurrentThread(), 0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());

View File

@ -1,12 +1,16 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -23,9 +27,27 @@ import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.AllergyIntolerance;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.r5.utils.IResourceValidator;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
@ -33,6 +55,7 @@ import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.AopTestUtils; import org.springframework.test.util.AopTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -45,6 +68,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class);
@ -56,6 +82,10 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvcc; private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvcc;
@Autowired @Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Autowired
private JpaValidationSupportChain myJpaValidationSupportChain;
@Autowired
private PlatformTransactionManager myTransactionManager;
/** /**
* Create a loinc valueset that expands to more results than the expander is willing to do * Create a loinc valueset that expands to more results than the expander is willing to do
@ -155,13 +185,11 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
} }
/** /**
* Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems * Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems
* * <p>
* We should generate a warning if a code can't be found but the codesystem is a fragment * We should generate a warning if a code can't be found but the codesystem is a fragment
*/ */
@Test @Test
@ -219,7 +247,6 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} }
} }
/** /**
* Create a loinc valueset that expands to more results than the expander is willing to do * Create a loinc valueset that expands to more results than the expander is willing to do
* in memory, and make sure we can still validate correctly, even if we're using * in memory, and make sure we can still validate correctly, even if we're using
@ -303,6 +330,115 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} }
/**
* Make sure that we do something sane when validating throws an unexpected exception
*/
@Test
public void testValidate_ValidationSupportThrowsException() {
IValidationSupport validationSupport = mock(IValidationSupport.class);
when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenAnswer(t -> {
// This will fail with a constraint error
try {
myResourceTableDao.save(new ResourceTable());
myResourceTableDao.flush();
} catch (Exception e) {
ourLog.info("Hit expected exception: {}", e.toString());
}
return null;
});
when(validationSupport.getFhirContext()).thenReturn(myFhirCtx);
myJpaValidationSupportChain.addValidationSupport(0, validationSupport);
try {
Observation obs = new Observation();
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
} finally {
myJpaValidationSupportChain.removeValidationSupport(validationSupport);
}
}
/**
* Make sure that we do something sane when validating throws an unexpected exception
*/
@Test
@Ignore
public void testValidate_TermSvcHasDatabaseRollback() {
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(() -> {
try {
myResourceTableDao.save(new ResourceTable());
myResourceTableDao.flush();
} catch (Exception e) {
ourLog.info("Hit expected exception: {}", e.toString());
}
});
Observation obs = new Observation();
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
}
/**
* Make sure that we do something sane when validating throws an unexpected exception
*/
@Test
public void testValidate_TermSvcHasNpe() {
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(() -> {
throw new NullPointerException("MY ERROR");
});
Observation obs = new Observation();
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE99999").setDisplay("Display 3");
try {
validateAndReturnOutcome(obs);
fail();
} catch (NullPointerException e) {
assertEquals("MY ERROR", e.getMessage());
}
}
@Test @Test
public void testValidateCodeableConceptWithNoSystem() { public void testValidateCodeableConceptWithNoSystem() {
AllergyIntolerance allergy = new AllergyIntolerance(); AllergyIntolerance allergy = new AllergyIntolerance();
@ -605,6 +741,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setMaximumExpansionSize(DaoConfig.DEFAULT_MAX_EXPANSION_SIZE); myDaoConfig.setMaximumExpansionSize(DaoConfig.DEFAULT_MAX_EXPANSION_SIZE);
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets()); myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(null);
} }
@Test @Test

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao; import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.ProxyUtil; import ca.uhn.fhir.test.utilities.ProxyUtil;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
@ -30,9 +31,9 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
public class IgInstallerTestDstu3 extends BaseJpaDstu3Test { public class IgInstallerDstu3Test extends BaseJpaDstu3Test {
private static final Logger ourLog = LoggerFactory.getLogger(IgInstallerTestDstu3.class); private static final Logger ourLog = LoggerFactory.getLogger(IgInstallerDstu3Test.class);
@Autowired @Autowired
private DaoConfig daoConfig; private DaoConfig daoConfig;
@Autowired @Autowired
@ -185,7 +186,7 @@ public class IgInstallerTestDstu3 extends BaseJpaDstu3Test {
); );
fail(); fail();
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
assertEquals("", e.getMessage()); assertEquals("Package ID nictiz.fhir.nl.stu3.questionnaires doesn't match expected: blah", e.getMessage());
} }
} }
@ -199,8 +200,8 @@ public class IgInstallerTestDstu3 extends BaseJpaDstu3Test {
.setPackageUrl("http://localhost:" + myPort + "/foo.tgz") .setPackageUrl("http://localhost:" + myPort + "/foo.tgz")
); );
fail(); fail();
} catch (InvalidRequestException e) { } catch (ResourceNotFoundException e) {
assertEquals("", e.getMessage()); assertEquals("Received HTTP 404 from URL: http://localhost:" + myPort + "/foo.tgz", e.getMessage());
} }
} }

View File

@ -19,7 +19,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
@Test @Test
public void testSavePackage() throws IOException { public void testSavePackage() throws IOException {
try (InputStream stream = IgInstallerTestDstu3.class.getResourceAsStream("/packages/basisprofil.de.tar.gz")) { try (InputStream stream = IgInstallerDstu3Test.class.getResourceAsStream("/packages/basisprofil.de.tar.gz")) {
myPackageCacheManager.addPackageToCache("basisprofil.de", "0.2.40", stream, "basisprofil.de"); myPackageCacheManager.addPackageToCache("basisprofil.de", "0.2.40", stream, "basisprofil.de");
} }

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.jpa.util;
import org.junit.Test;
import static org.junit.Assert.fail;
public class JpaClasspathTest {
/**
* Make sure no dependencies start bringing in log4j - This makes hibernate decide to start using log4j instead of
* slf4j which is super annoying..
*/
@Test
public void testNoLog4jOnClasspath() {
try {
Class.forName("org.apache.logging.log4j.status.StatusLogger");
fail("org.apache.logging.log4j.status.StatusLogger" + " found on classpath - Make sure log4j isn't being introduced");
} catch (ClassNotFoundException theE) {
// good
}
}
}

View File

@ -122,6 +122,14 @@ public class ValidationSupportChain implements IValidationSupport {
myChain.add(theIndex, theValidationSupport); myChain.add(theIndex, theValidationSupport);
} }
/**
* Removes an item from the chain. Note that this method is mostly intended for testing. Removing items from the chain while validation is
* actually occurring is not an expected use case for this class.
*/
public void removeValidationSupport(IValidationSupport theValidationSupport) {
myChain.remove(theValidationSupport);
}
@Override @Override
public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {

View File

@ -309,7 +309,9 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
*/ */
public void invalidateCaches() { public void invalidateCaches() {
myValidationSupport.invalidateCaches(); myValidationSupport.invalidateCaches();
myWrappedWorkerContext.invalidateCaches(); if (myWrappedWorkerContext != null) {
myWrappedWorkerContext.invalidateCaches();
}
} }