From 0d3ad622b5f64796cb4c82e272caf85ce449e910 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 10 Jun 2020 05:26:21 -0400 Subject: [PATCH] Correct Validation Performance Regression (#1895) * Validation performance regression * Optimize validation * More cleanup * Add changelog * Test fixes * Build fixes --- .../support/ConceptValidationOptions.java | 9 ++ ...jpa-validation-performance-regression.yaml | 5 + .../ca/uhn/fhir/jpa/config/BaseConfig.java | 1 + .../fhir/jpa/config/BaseConfigDstu3Plus.java | 6 +- .../uhn/fhir/jpa/config/BaseDstu2Config.java | 20 +-- ...JpaPersistedResourceValidationSupport.java | 133 ++++++++++-------- .../jpa/entity/TermCodeSystemVersion.java | 19 ++- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 54 ++++--- .../uhn/fhir/jpa/term/api/ITermReadSvc.java | 2 - .../validation/JpaValidationSupportChain.java | 8 +- .../uhn/fhir/jpa/config/TestDstu2Config.java | 5 +- .../java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java | 14 ++ .../fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java | 3 +- .../FhirResourceDaoDstu3ValidateTest.java | 9 ++ .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 3 +- .../r4/FhirResourceDaoR4QueryCountTest.java | 60 +++++++- .../dao/r4/FhirResourceDaoR4ValidateTest.java | 21 +++ .../ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java | 3 +- .../uhn/fhirtest/config/TestDstu2Config.java | 6 +- .../src/main/resources/application.yml | 2 +- .../support/CachingValidationSupport.java | 19 +++ pom.xml | 2 +- 22 files changed, 292 insertions(+), 112 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1895-fix-jpa-validation-performance-regression.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ConceptValidationOptions.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ConceptValidationOptions.java index 7d1d3f2d135..f70fefca646 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ConceptValidationOptions.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ConceptValidationOptions.java @@ -20,6 +20,9 @@ package ca.uhn.fhir.context.support; * #L% */ +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + public class ConceptValidationOptions { public boolean isInferSystem() { @@ -33,4 +36,10 @@ public class ConceptValidationOptions { private boolean myInferSystem; + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("inferSystem", myInferSystem) + .toString(); + } } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1895-fix-jpa-validation-performance-regression.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1895-fix-jpa-validation-performance-regression.yaml new file mode 100644 index 00000000000..d7c082340b9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1895-fix-jpa-validation-performance-regression.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 1895 +title: "HAPI FHIR 5.0.0 introduced a regressin in JPA validator performance, where a number of unnecessary database lookups + were introduced. This has been corrected." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index ce38be34068..bae2105cb45 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -114,6 +114,7 @@ import java.util.Date; public abstract class BaseConfig { public static final String JPA_VALIDATION_SUPPORT_CHAIN = "myJpaValidationSupportChain"; + public static final String JPA_VALIDATION_SUPPORT = "myJpaValidationSupport"; public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor"; public static final String GRAPHQL_PROVIDER_NAME = "myGraphQLProvider"; public static final String PERSISTED_JPA_BUNDLE_PROVIDER = "PersistedJpaBundleProvider"; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java index 8f27e2f0a41..f67f43c0c48 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java @@ -65,16 +65,16 @@ public abstract class BaseConfigDstu3Plus extends BaseConfig { public abstract ITermVersionAdapterSvc terminologyVersionAdapterSvc(); @Bean(name = "myDefaultProfileValidationSupport") - public IValidationSupport defaultProfileValidationSupport() { + public DefaultProfileValidationSupport defaultProfileValidationSupport() { return new DefaultProfileValidationSupport(fhirContext()); } @Bean(name = JPA_VALIDATION_SUPPORT_CHAIN) - public ValidationSupportChain jpaValidationSupportChain() { + public JpaValidationSupportChain jpaValidationSupportChain() { return new JpaValidationSupportChain(fhirContext()); } - @Bean(name = "myJpaValidationSupport") + @Bean(name = JPA_VALIDATION_SUPPORT) public IValidationSupport jpaValidationSupport() { return new JpaPersistedResourceValidationSupport(fhirContext()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index 3dd5bb69a27..34e9ba7df84 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -86,28 +86,30 @@ public class BaseDstu2Config extends BaseConfig { @Bean(name = "myInstanceValidator") @Lazy - public IInstanceValidatorModule instanceValidator() { - ValidationSupportChain validationSupportChain = validationSupportChain(); - CachingValidationSupport cachingValidationSupport = new CachingValidationSupport(new HapiToHl7OrgDstu2ValidatingSupportWrapper(validationSupportChain)); + public IInstanceValidatorModule instanceValidator(ValidationSupportChain theValidationSupportChain) { + CachingValidationSupport cachingValidationSupport = new CachingValidationSupport(new HapiToHl7OrgDstu2ValidatingSupportWrapper(theValidationSupportChain)); FhirInstanceValidator retVal = new FhirInstanceValidator(cachingValidationSupport); retVal.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning); return retVal; } + @Bean(name = "myDefaultProfileValidationSupport") + public DefaultProfileValidationSupport defaultProfileValidationSupport() { + return new DefaultProfileValidationSupport(fhirContext()); + } + @Bean(name = JPA_VALIDATION_SUPPORT_CHAIN) - public ValidationSupportChain validationSupportChain() { - DefaultProfileValidationSupport defaultProfileValidationSupport = new DefaultProfileValidationSupport(fhirContext()); + public ValidationSupportChain validationSupportChain(DefaultProfileValidationSupport theDefaultProfileValidationSupport) { InMemoryTerminologyServerValidationSupport inMemoryTerminologyServer = new InMemoryTerminologyServerValidationSupport(fhirContextDstu2()); IValidationSupport jpaValidationSupport = jpaValidationSupportDstu2(); CommonCodeSystemsTerminologyService commonCodeSystemsTermSvc = new CommonCodeSystemsTerminologyService(fhirContext()); - return new ValidationSupportChain(defaultProfileValidationSupport, jpaValidationSupport, inMemoryTerminologyServer, commonCodeSystemsTermSvc); + return new ValidationSupportChain(theDefaultProfileValidationSupport, jpaValidationSupport, inMemoryTerminologyServer, commonCodeSystemsTermSvc); } @Primary - @Bean + @Bean(name = JPA_VALIDATION_SUPPORT) public IValidationSupport jpaValidationSupportDstu2() { - JpaPersistedResourceValidationSupport retVal = new JpaPersistedResourceValidationSupport(fhirContextDstu2()); - return retVal; + return new JpaPersistedResourceValidationSupport(fhirContextDstu2()); } @Bean(name = "myResourceCountsCache") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java index 4af6519f71a..a37904f02d5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java @@ -28,6 +28,8 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.UriParam; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -43,6 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; import javax.transaction.Transactional; +import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -57,6 +60,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport private static final Logger ourLog = LoggerFactory.getLogger(JpaPersistedResourceValidationSupport.class); private final FhirContext myFhirContext; + private final IBaseResource myNoMatch; @Autowired private DaoRegistry myDaoRegistry; @@ -65,6 +69,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport private Class myValueSetType; private Class myQuestionnaireType; private Class myImplementationGuideType; + private Cache myLoadCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(); /** * Constructor @@ -73,6 +78,8 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport super(); Validate.notNull(theFhirContext); myFhirContext = theFhirContext; + + myNoMatch = myFhirContext.getResourceDefinition("Basic").newInstance(); } @@ -99,77 +106,86 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport return null; } - IdType id = new IdType(theUri); - boolean localReference = false; - if (id.hasBaseUrl() == false && id.hasIdPart() == true) { - localReference = true; - } + String key = theClass.getSimpleName() + " " + theUri; + IBaseResource fetched = myLoadCache.get(key, t -> { + IdType id = new IdType(theUri); + boolean localReference = false; + if (id.hasBaseUrl() == false && id.hasIdPart() == true) { + localReference = true; + } - String resourceName = myFhirContext.getResourceType(theClass); - IBundleProvider search; - if ("ValueSet".equals(resourceName)) { - if (localReference) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(IAnyResource.SP_RES_ID, new StringParam(theUri)); - search = myDaoRegistry.getResourceDao("ValueSet").search(params); - if (search.size() == 0) { - params = new SearchParameterMap(); + String resourceName = myFhirContext.getResourceType(theClass); + IBundleProvider search; + if ("ValueSet".equals(resourceName)) { + if (localReference) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(IAnyResource.SP_RES_ID, new StringParam(theUri)); + search = myDaoRegistry.getResourceDao("ValueSet").search(params); + if (search.size() == 0) { + params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(ValueSet.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao("ValueSet").search(params); + } + } else { + SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(1); params.add(ValueSet.SP_URL, new UriParam(theUri)); search = myDaoRegistry.getResourceDao("ValueSet").search(params); } - } else { + } else if ("StructureDefinition".equals(resourceName)) { + // Don't allow the core FHIR definitions to be overwritten + if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { + String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length()); + if (myFhirContext.getElementDefinition(typeName) != null) { + return myNoMatch; + } + } SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(1); - params.add(ValueSet.SP_URL, new UriParam(theUri)); - search = myDaoRegistry.getResourceDao("ValueSet").search(params); - } - } else if ("StructureDefinition".equals(resourceName)) { - // Don't allow the core FHIR definitions to be overwritten - if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { - String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length()); - if (myFhirContext.getElementDefinition(typeName) != null) { - return null; + params.add(StructureDefinition.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao("StructureDefinition").search(params); + } else if ("Questionnaire".equals(resourceName)) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + if (localReference || myFhirContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU2)) { + params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart())); + } else { + params.add(Questionnaire.SP_URL, new UriParam(id.getValue())); } - } - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(StructureDefinition.SP_URL, new UriParam(theUri)); - search = myDaoRegistry.getResourceDao("StructureDefinition").search(params); - } else if ("Questionnaire".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - if (localReference || myFhirContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU2)) { - params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart())); + search = myDaoRegistry.getResourceDao("Questionnaire").search(params); + } else if ("CodeSystem".equals(resourceName)) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(CodeSystem.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao(resourceName).search(params); + } else if ("ImplementationGuide".equals(resourceName)) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(ImplementationGuide.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao("ImplementationGuide").search(params); } else { - params.add(Questionnaire.SP_URL, new UriParam(id.getValue())); + throw new IllegalArgumentException("Can't fetch resource type: " + resourceName); } - search = myDaoRegistry.getResourceDao("Questionnaire").search(params); - } else if ("CodeSystem".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(CodeSystem.SP_URL, new UriParam(theUri)); - search = myDaoRegistry.getResourceDao(resourceName).search(params); - } else if ("ImplementationGuide".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(ImplementationGuide.SP_URL, new UriParam(theUri)); - search = myDaoRegistry.getResourceDao("ImplementationGuide").search(params); - } else { - throw new IllegalArgumentException("Can't fetch resource type: " + resourceName); - } - Integer size = search.size(); - if (size == null || size == 0) { + Integer size = search.size(); + if (size == null || size == 0) { + return myNoMatch; + } + + if (size > 1) { + ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri); + } + + return search.getResources(0, 1).get(0); + }); + + if (fetched == myNoMatch) { return null; } - if (size > 1) { - ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri); - } - - return (T) search.getResources(0, 1).get(0); + return (T) fetched; } @Override @@ -192,4 +208,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport } + public void clearCaches() { + myLoadCache.invalidateAll(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java index c87f76e3d3e..5b16f5811e9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java @@ -27,7 +27,19 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -127,6 +139,11 @@ public class TermCodeSystemVersion implements Serializable { return this; } + public TermCodeSystemVersion setId(Long theId) { + myId = theId; + return this; + } + @Override public boolean equals(Object theO) { if (this == theO) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index c4bd44851a9..19544975083 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; @@ -173,8 +174,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { public static final int DEFAULT_FETCH_SIZE = 250; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseTermReadSvcImpl.class); private static final ValueSetExpansionOptions DEFAULT_EXPANSION_OPTIONS = new ValueSetExpansionOptions(); + private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L); private static boolean ourLastResultsFromTranslationCache; // For testing. private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing. + private final int myFetchSize = DEFAULT_FETCH_SIZE; + private final Cache myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(); @Autowired protected DaoRegistry myDaoRegistry; @Autowired @@ -209,7 +213,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { private DaoConfig myDaoConfig; private Cache> myTranslationCache; private Cache> myTranslationWithReverseCache; - private final int myFetchSize = DEFAULT_FETCH_SIZE; private TransactionTemplate myTxTemplate; @Autowired private PlatformTransactionManager myTransactionManager; @@ -227,12 +230,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { private ITermCodeSystemStorageSvc myConceptStorageSvc; @Autowired private ApplicationContext myApplicationContext; + @Autowired + private DefaultProfileValidationSupport myDefaultProfileValidationSupport; private volatile IValidationSupport myJpaValidationSupport; private volatile IValidationSupport myValidationSupport; @Override public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { - return supportsSystem(theSystem); + TermCodeSystemVersion cs = getCurrentCodeSystemVersion(theSystem); + return cs != null; } private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) { @@ -283,16 +289,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { * This method is present only for unit tests, do not call from client code */ @VisibleForTesting - public void clearTranslationCache() { + public void clearCaches() { myTranslationCache.invalidateAll(); - } - - /** - * This method is present only for unit tests, do not call from client code - */ - @VisibleForTesting() - public void clearTranslationWithReverseCache() { myTranslationWithReverseCache.invalidateAll(); + myCodeSystemCurrentVersionCache.invalidateAll(); } public void deleteConceptMap(ResourceTable theResourceTable) { @@ -1289,15 +1289,35 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY); return txTemplate.execute(t -> { - TermCodeSystemVersion csv = null; - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theCodeSystem); - if (cs != null && cs.getCurrentVersion() != null) { - csv = cs.getCurrentVersion(); + TermCodeSystemVersion csv = getCurrentCodeSystemVersion(theCodeSystem); + if (csv == null) { + return null; } return myConceptDao.findByCodeSystemAndCode(csv, theCode); }); } + @Nullable + private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) { + TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> { + TermCodeSystemVersion csv = null; + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri); + if (cs != null && cs.getCurrentVersion() != null) { + csv = cs.getCurrentVersion(); + } + if (csv != null) { + return csv; + } else { + return NO_CURRENT_VERSION; + } + }); + if (retVal == NO_CURRENT_VERSION) { + return null; + } + return retVal; + } + + @Transactional(propagation = Propagation.REQUIRED) @Override public Set findCodesAbove(Long theCodeSystemResourcePid, Long theCodeSystemVersionPid, String theCode) { @@ -1715,12 +1735,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { return null; } - @Override - public boolean supportsSystem(String theSystem) { - TermCodeSystem cs = getCodeSystem(theSystem); - return cs != null; - } - private ArrayList toVersionIndependentConcepts(String theSystem, Set codes) { ArrayList retVal = new ArrayList<>(codes.size()); for (TermConcept next : codes) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java index 7b4fdb35225..9411117e80b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java @@ -94,8 +94,6 @@ public interface ITermReadSvc extends IValidationSupport { void storeTermValueSet(ResourceTable theResourceTable, ValueSet theValueSet); - boolean supportsSystem(String theCodeSystem); - List translate(TranslationRequest theTranslationRequest); List translateWithReverse(TranslationRequest theTranslationRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java index 49ffcacb283..a26b763f963 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java @@ -63,12 +63,12 @@ public class JpaValidationSupportChain extends ValidationSupportChain { @PostConstruct public void postConstruct() { - addValidationSupport((IValidationSupport) new CommonCodeSystemsTerminologyService(myFhirContext)); + addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext)); addValidationSupport(myDefaultProfileValidationSupport); addValidationSupport(myJpaValidationSupport); - addValidationSupport((IValidationSupport) myTerminologyService); - addValidationSupport((IValidationSupport) new SnapshotGeneratingValidationSupport(myFhirContext)); - addValidationSupport((IValidationSupport) new InMemoryTerminologyServerValidationSupport(myFhirContext)); + addValidationSupport(myTerminologyService); + addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext)); + addValidationSupport(new InMemoryTerminologyServerValidationSupport(myFhirContext)); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index 92dd30de97b..ee52d38d72e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.validation.IInstanceValidatorModule; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; @@ -152,12 +153,12 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { */ @Bean @Lazy - public RequestValidatingInterceptor requestValidatingInterceptor() { + public RequestValidatingInterceptor requestValidatingInterceptor(IInstanceValidatorModule theInstanceValidator) { RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor(); requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidator()); + requestValidator.addValidatorModule(theInstanceValidator); return requestValidator; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 2a7c080cc19..654af45e5ce 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.executor.InterceptorService; @@ -9,6 +10,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; +import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.util.JpaConstants; @@ -30,6 +32,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.test.utilities.LoggingRule; +import ca.uhn.fhir.test.utilities.ProxyUtil; import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.StopWatch; @@ -54,6 +57,8 @@ import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.PlatformTransactionManager; @@ -125,6 +130,10 @@ public abstract class BaseJpaTest extends BaseTest { private IdHelperService myIdHelperService; @Autowired private MemoryCacheService myMemoryCacheService; + @Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT) + @Autowired + private IValidationSupport myJpaPersistedValidationSupport; + @After public void afterPerformCleanup() { @@ -138,6 +147,11 @@ public abstract class BaseJpaTest extends BaseTest { if (myMemoryCacheService != null) { myMemoryCacheService.invalidateAllCaches(); } + if (myJpaPersistedValidationSupport != null) { + ProxyUtil.getSingletonTarget(myJpaPersistedValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches(); + } + + } @After diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index 6b77b901256..ba243f8368e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -353,8 +353,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @After public void afterClearTerminologyCaches() { BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); - baseHapiTerminologySvc.clearTranslationCache(); - baseHapiTerminologySvc.clearTranslationWithReverseCache(); + baseHapiTerminologySvc.clearCaches(); BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache(); BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); TermDeferredStorageSvcImpl deferredSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java index 84a06e342ea..f94807291b3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java @@ -1,5 +1,8 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.config.BaseConfig; +import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -7,6 +10,7 @@ import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.test.utilities.ProxyUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.validation.IValidatorModule; import org.apache.commons.io.IOUtils; @@ -23,6 +27,7 @@ import org.junit.AfterClass; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.util.AopTestUtils; import java.io.IOException; @@ -40,6 +45,9 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test { private CachingValidationSupport myValidationSupport; @Autowired private FhirInstanceValidator myFhirInstanceValidator; + @Autowired + @Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT) + private IValidationSupport myPersistedResourceValidationSupport; @Test public void testValidateChangedQuestionnaire() { @@ -78,6 +86,7 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test { ourLog.info("Clearing cache"); myValidationSupport.invalidateCaches(); myFhirInstanceValidator.invalidateCaches(); + ProxyUtil.getSingletonTarget(myPersistedResourceValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches(); try { myQuestionnaireResponseDao.validate(qr, null, null, null, null, null, null); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index 478c7c90b27..be8242b775f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -482,8 +482,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBui @After public void afterClearTerminologyCaches() { BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); - baseHapiTerminologySvc.clearTranslationCache(); - baseHapiTerminologySvc.clearTranslationWithReverseCache(); + baseHapiTerminologySvc.clearCaches(); BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache(); BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index 894e7702130..cab755946a2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -9,11 +9,16 @@ import ca.uhn.fhir.rest.param.ReferenceParam; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeSystem; +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.Patient; import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.ServiceRequest; +import org.hl7.fhir.r4.model.StringType; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -25,7 +30,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.mockito.ArgumentMatchers.contains; public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class); @@ -114,6 +118,56 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + + @Test + public void testValidate() { + + CodeSystem cs = new CodeSystem(); + cs.setUrl("http://foo/cs"); + cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + cs.addConcept().setCode("bar-1").setDisplay("Bar 1"); + cs.addConcept().setCode("bar-2").setDisplay("Bar 2"); + myCodeSystemDao.create(cs); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(cs)); + + Observation obs = new Observation(); +// obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2"); + obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED).setDivAsString("
Hello
"); + 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(Observation.ObservationStatus.FINAL); + obs.setValue(new StringType("This is the value")); + obs.getCode().addCoding().setSystem("http://foo/cs").setCode("bar-1"); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs)); + + // Validate once + myCaptureQueriesListener.clear(); + myObservationDao.validate(obs, null, null, null, null, null, null); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertEquals(8, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + myCaptureQueriesListener.logUpdateQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + myCaptureQueriesListener.logInsertQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + myCaptureQueriesListener.logDeleteQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + + // Validate again (should rely only on caches) + myCaptureQueriesListener.clear(); + myObservationDao.validate(obs, null, null, null, null, null, null); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + myCaptureQueriesListener.logUpdateQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + myCaptureQueriesListener.logInsertQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + myCaptureQueriesListener.logDeleteQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + } + + @Test public void testVRead() { IIdType id = runInTransaction(() -> { @@ -488,12 +542,10 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { } - - @Test public void testTransactionWithMultipleReferences() { Bundle input = new Bundle(); - + Patient patient = new Patient(); patient.setId(IdType.newRandomUuid()); patient.setActive(true); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index fd619b3f736..f143c920ad4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -136,6 +136,27 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { oo = validateAndReturnOutcome(obs); assertEquals(encode(oo), "Unknown code 'http://terminology.hl7.org/CodeSystem/observation-category#FOO'", oo.getIssueFirstRep().getDiagnostics()); + // Make sure we're caching the validations as opposed to hitting the DB every time + myCaptureQueriesListener.clear(); + obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); + obs.getCode().getCoding().clear(); + obs.getCategory().clear(); + obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs"); + obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE4").setDisplay("Display 3"); + oo = validateAndReturnOutcome(obs); + assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics()); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + + myCaptureQueriesListener.clear(); + obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); + obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE4").setDisplay("Display 3"); + oo = validateAndReturnOutcome(obs); + assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics()); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + + + + } /** diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index fdd69f0a730..af581918027 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -429,8 +429,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @After public void afterClearTerminologyCaches() { BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); - baseHapiTerminologySvc.clearTranslationCache(); - baseHapiTerminologySvc.clearTranslationWithReverseCache(); + baseHapiTerminologySvc.clearCaches(); BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache(); BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); TermDeferredStorageSvcImpl deferredStorageSvc = AopTestUtils.getTargetObject(myTermDeferredStorageSvc); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index 872ac53a597..59c01e3da7d 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor; import org.apache.commons.dbcp2.BasicDataSource; @@ -133,15 +134,16 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { /** * Bean which validates incoming requests + * @param theInstanceValidator */ @Bean @Lazy - public RequestValidatingInterceptor requestValidatingInterceptor() { + public RequestValidatingInterceptor requestValidatingInterceptor(IValidatorModule theInstanceValidator) { RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor(); requestValidator.setFailOnSeverity(null); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidator()); + requestValidator.addValidatorModule(theInstanceValidator); requestValidator.setIgnoreValidatorExceptions(true); return requestValidator; diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml index 2c2f591eb69..441dd26f0af 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml @@ -18,7 +18,7 @@ spring: enabled: true hapi: fhir: - version: dstu3 + version: DSTU3 server: path: /fhir/* rest: diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java index 6b79ca07119..7a5468aea81 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java @@ -1,21 +1,27 @@ package org.hl7.fhir.common.hapi.validation.support; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.checkerframework.checker.nullness.qual.Nullable; +import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Function; import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isNotBlank; @SuppressWarnings("unchecked") public class CachingValidationSupport extends BaseValidationSupportWrapper implements IValidationSupport { @@ -96,6 +102,19 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple } + @Override + public IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + + BaseRuntimeChildDefinition urlChild = myCtx.getResourceDefinition(theValueSet).getChildByName("url"); + Optional valueSetUrl = urlChild.getAccessor().getValues(theValueSet).stream().map(t -> ((IPrimitiveType) t).getValueAsString()).filter(t->isNotBlank(t)).findFirst(); + if (valueSetUrl.isPresent()) { + String key = "validateCodeInValueSet " + theValidationOptions.toString() + " " + defaultString(theCodeSystem, "(null)") + " " + defaultString(theCode, "(null)") + " " + defaultString(theDisplay, "(null)") + " " + valueSetUrl.get(); + return loadFromCache(myValidateCodeCache, key, t-> super.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet)); + } + + return super.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet); + } + @Override public void invalidateCaches() { myLookupCodeCache.invalidateAll(); diff --git a/pom.xml b/pom.xml index 62212ea60bc..ab2870c4b71 100644 --- a/pom.xml +++ b/pom.xml @@ -894,7 +894,7 @@ org.hl7.fhir.testcases fhir-test-cases - 1.1.14-SNAPSHOT + 1.1.14 org.jetbrains