From 3dfb6959c14c7c2dddf16a0b4bdcb565870916ed Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 29 Oct 2019 11:35:26 -0400 Subject: [PATCH 01/10] Don't reload terminology resources when nothing has changed --- .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 11 ++++++---- .../dstu3/FhirResourceDaoConceptMapDstu3.java | 20 ++++++++++--------- .../dstu3/FhirResourceDaoValueSetDstu3.java | 2 +- .../dao/r4/FhirResourceDaoCodeSystemR4.java | 8 +++++--- .../dao/r4/FhirResourceDaoConceptMapR4.java | 12 ++++++----- .../jpa/dao/r4/FhirResourceDaoValueSetR4.java | 2 +- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 8 +++++--- .../dao/r5/FhirResourceDaoConceptMapR5.java | 14 +++++++------ .../jpa/dao/r5/FhirResourceDaoValueSetR5.java | 2 +- 9 files changed, 46 insertions(+), 33 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 21a4e28a615..2f92d732643 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -131,13 +131,16 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - if (myDaoConfig.isPreExpandValueSets()) { + if (myDaoConfig.isPreExpandValueSets() && !retVal.isUnchangedInCurrentOperation()) { if (retVal.getDeleted() == null) { try { ValueSet valueSet = (ValueSet) theResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java index 46d38a2fd06..ce3ddfa992b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java @@ -135,10 +135,12 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4 i boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - CodeSystem cs = (CodeSystem) theResource; - addPidToResource(theEntity, theResource); + if (!retVal.isUnchangedInCurrentOperation()) { + CodeSystem cs = (CodeSystem) theResource; + addPidToResource(theEntity, theResource); - myTerminologyCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(cs, theEntity); + myTerminologyCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(cs, theEntity); + } return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java index d715b273018..1e2c9c2264b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java @@ -160,11 +160,13 @@ public class FhirResourceDaoConceptMapR4 extends FhirResourceDaoR4 i boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - if (retVal.getDeleted() == null) { - ConceptMap conceptMap = (ConceptMap) theResource; - myHapiTerminologySvc.storeTermConceptMapAndChildren(retVal, conceptMap); - } else { - myHapiTerminologySvc.deleteConceptMapAndChildren(retVal); + if (!retVal.isUnchangedInCurrentOperation()) { + if (retVal.getDeleted() == null) { + ConceptMap conceptMap = (ConceptMap) theResource; + myHapiTerminologySvc.storeTermConceptMapAndChildren(retVal, conceptMap); + } else { + myHapiTerminologySvc.deleteConceptMapAndChildren(retVal); + } } return retVal; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java index 57db07cf3a7..e938e32a8cc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java @@ -391,7 +391,7 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4 imple boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - if (myDaoConfig.isPreExpandValueSets()) { + if (myDaoConfig.isPreExpandValueSets() && !retVal.isUnchangedInCurrentOperation()) { if (retVal.getDeleted() == null) { ValueSet valueSet = (ValueSet) theResource; myHapiTerminologySvc.storeTermValueSet(retVal, valueSet); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java index fbdea21197e..b8c857cc409 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java @@ -134,11 +134,13 @@ public class FhirResourceDaoCodeSystemR5 extends FhirResourceDaoR5 i public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); + if (!retVal.isUnchangedInCurrentOperation()) { - CodeSystem cs = (CodeSystem) theResource; - addPidToResource(theEntity, theResource); + CodeSystem cs = (CodeSystem) theResource; + addPidToResource(theEntity, theResource); - myTerminologyCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem(cs), theEntity); + myTerminologyCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem(cs), theEntity); + } return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java index ea7b2525463..a9b124d25de 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java @@ -160,14 +160,16 @@ public class FhirResourceDaoConceptMapR5 extends FhirResourceDaoR5 i public ResourceTable updateEntity(RequestDetails theRequestDetails, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); + if (!retVal.isUnchangedInCurrentOperation()) { - if (retVal.getDeleted() == null) { - ConceptMap conceptMap = (ConceptMap) theResource; - myHapiTerminologySvc.storeTermConceptMapAndChildren(retVal, org.hl7.fhir.convertors.conv40_50.ConceptMap.convertConceptMap(conceptMap)); - } else { - myHapiTerminologySvc.deleteConceptMapAndChildren(retVal); + if (retVal.getDeleted() == null) { + ConceptMap conceptMap = (ConceptMap) theResource; + myHapiTerminologySvc.storeTermConceptMapAndChildren(retVal, org.hl7.fhir.convertors.conv40_50.ConceptMap.convertConceptMap(conceptMap)); + } else { + myHapiTerminologySvc.deleteConceptMapAndChildren(retVal); + } } - + return retVal; } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java index ce1cbdcc515..a1adefbd9be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java @@ -403,7 +403,7 @@ public class FhirResourceDaoValueSetR5 extends FhirResourceDaoR5 imple boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - if (myDaoConfig.isPreExpandValueSets()) { + if (myDaoConfig.isPreExpandValueSets() && !retVal.isUnchangedInCurrentOperation()) { if (retVal.getDeleted() == null) { ValueSet valueSet = (ValueSet) theResource; myHapiTerminologySvc.storeTermValueSet(retVal, org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSet)); From 84803a02ef1f2e72a98a7a576c9932cbe614b29e Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 29 Oct 2019 15:54:49 -0400 Subject: [PATCH 02/10] Preserve line numbers in XML validation (#1567) * Preserve line numbers in XML validation * Test fixes --- .../jpa/provider/dstu3/ResourceProviderDstu3Test.java | 10 ++-------- .../fhir/jpa/provider/r4/ResourceProviderR4Test.java | 4 +--- .../fhir/common/hapi/validation/ValidatorWrapper.java | 6 +++++- .../r4/validation/FhirInstanceValidatorR4Test.java | 2 ++ src/site/xdoc/download.xml.vm | 10 ++++++++++ 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index 0439df50d23..69a03290a8a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -4080,22 +4080,16 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { Patient patient = new Patient(); patient.addName().addGiven("James"); patient.setBirthDateElement(new DateType("2011-02-02")); - patient.addContact().setGender(AdministrativeGender.MALE); String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(patient); HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate"); post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); - CloseableHttpResponse response = ourHttpClient.execute(post); - try { + try (CloseableHttpResponse response = ourHttpClient.execute(post)) { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); - assertEquals(412, response.getStatusLine().getStatusCode()); + assertEquals(200, response.getStatusLine().getStatusCode()); assertThat(resp, not(containsString("Resource has no id"))); - assertThat(resp, containsString("")); - } finally { - IOUtils.closeQuietly(response.getEntity().getContent()); - response.close(); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index d4ce979de70..ca67457117f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -5308,7 +5308,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Patient patient = new Patient(); patient.addName().addGiven("James"); patient.setBirthDateElement(new DateType("2011-02-02")); - patient.addContact().setGender(AdministrativeGender.MALE); String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(patient); HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate"); @@ -5317,9 +5316,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse response = ourHttpClient.execute(post)) { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); - assertEquals(412, response.getStatusLine().getStatusCode()); + assertEquals(200, response.getStatusLine().getStatusCode()); assertThat(resp, not(containsString("Resource has no id"))); - assertThat(resp, containsString("")); } } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/ValidatorWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/ValidatorWrapper.java index 27ddb19b07f..61f1358ec1b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/ValidatorWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/ValidatorWrapper.java @@ -111,7 +111,11 @@ public class ValidatorWrapper { profileSet.getCanonical().add(new ValidationProfileSet.ProfileRegistration(nextProfile, true)); } - v.validate(null, messages, document, profileSet); + String resourceAsString = theValidationContext.getResourceAsString(); + InputStream inputStream = new ReaderInputStream(new StringReader(resourceAsString), Charsets.UTF_8); + + Manager.FhirFormat format = Manager.FhirFormat.XML; + v.validate(null, messages, inputStream, format, profileSet); } else if (encoding == EncodingEnum.JSON) { diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index 881f0e95d7d..a9f9cbdda01 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -799,6 +799,8 @@ public class FhirInstanceValidatorR4Test extends BaseTest { ourLog.info(output.getMessages().get(0).getMessage()); assertEquals("/f:Patient", output.getMessages().get(0).getLocationString()); assertEquals("Undefined element 'foo'", output.getMessages().get(0).getMessage()); + assertEquals(28, output.getMessages().get(0).getLocationCol().intValue()); + assertEquals(4, output.getMessages().get(0).getLocationLine().intValue()); } @Test diff --git a/src/site/xdoc/download.xml.vm b/src/site/xdoc/download.xml.vm index 804b0f79aad..ac699047c8f 100644 --- a/src/site/xdoc/download.xml.vm +++ b/src/site/xdoc/download.xml.vm @@ -262,6 +262,16 @@ 4.0.0 4.1.0-e0e3caf9ba + + HAPI FHIR 4.1.0 + JDK8 + + 1.0.2 + 1.4.0 + 3.0.1 + 4.0.0 + 4.1.0-1a7623d866 + From 9c852283e92d091fea7979e0825f3a26ff5ff952 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 30 Oct 2019 05:31:25 -0400 Subject: [PATCH 03/10] Correctly handle "special" search params in CS (#1569) * Correctly handle "special" search params in CS * Add another test --- .../provider/r4/JpaConformanceProviderR4.java | 104 ++++++------ .../provider/r5/JpaConformanceProviderR5.java | 83 +++++----- .../reindex/ResourceReindexingSvcImpl.java | 5 + .../r4/BaseResourceProviderR4Test.java | 4 +- .../fhir/jpa/provider/r4/ServerR4Test.java | 31 +++- .../fhir/jpa/provider/r5/ServerR5Test.java | 151 ++++++++++++++++++ .../ResourceReindexingSvcImplTest.java | 13 ++ .../ServerCapabilityStatementProvider.java | 131 ++++++--------- src/changes/changes.xml | 4 + 9 files changed, 348 insertions(+), 178 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java index c7f03121c00..ced5735562b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java @@ -19,24 +19,25 @@ package ca.uhn.fhir.jpa.provider.r4; * limitations under the License. * #L% */ -import java.util.*; - -import javax.servlet.http.HttpServletRequest; - -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.CapabilityStatement.*; -import org.hl7.fhir.r4.model.Enumerations.SearchParamType; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ExtensionConstants; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CapabilityStatement.*; +import org.hl7.fhir.r4.model.Enumerations.SearchParamType; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -50,17 +51,17 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S private boolean myIncludeResourceCounts; private RestfulServer myRestfulServer; private IFhirSystemDao mySystemDao; - + /** * Constructor */ @CoverageIgnore - public JpaConformanceProviderR4(){ + public JpaConformanceProviderR4() { super(); super.setCache(false); setIncludeResourceCounts(true); } - + /** * Constructor */ @@ -94,7 +95,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) { nextResource.setVersioning(ResourceVersionPolicy.VERSIONEDUPDATE); - + ConditionalDeleteStatus conditionalDelete = nextResource.getConditionalDelete(); if (conditionalDelete == ConditionalDeleteStatus.MULTIPLE && myDaoConfig.isAllowMultipleDelete() == false) { nextResource.setConditionalDelete(ConditionalDeleteStatus.SINGLE); @@ -109,7 +110,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S nextResource.getSearchParam().clear(); String resourceName = nextResource.getType(); RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); - Collection searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef); + Collection searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef); for (RuntimeSearchParam runtimeSp : searchParams) { CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam(); @@ -117,37 +118,40 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S confSp.setDocumentation(runtimeSp.getDescription()); confSp.setDefinition(runtimeSp.getUri()); switch (runtimeSp.getParamType()) { - case COMPOSITE: - confSp.setType(SearchParamType.COMPOSITE); - break; - case DATE: - confSp.setType(SearchParamType.DATE); - break; - case NUMBER: - confSp.setType(SearchParamType.NUMBER); - break; - case QUANTITY: - confSp.setType(SearchParamType.QUANTITY); - break; - case REFERENCE: - confSp.setType(SearchParamType.REFERENCE); - break; - case STRING: - confSp.setType(SearchParamType.STRING); - break; - case TOKEN: - confSp.setType(SearchParamType.TOKEN); - break; - case URI: - confSp.setType(SearchParamType.URI); - break; - case HAS: - // Shouldn't happen - break; + case COMPOSITE: + confSp.setType(SearchParamType.COMPOSITE); + break; + case DATE: + confSp.setType(SearchParamType.DATE); + break; + case NUMBER: + confSp.setType(SearchParamType.NUMBER); + break; + case QUANTITY: + confSp.setType(SearchParamType.QUANTITY); + break; + case REFERENCE: + confSp.setType(SearchParamType.REFERENCE); + break; + case STRING: + confSp.setType(SearchParamType.STRING); + break; + case TOKEN: + confSp.setType(SearchParamType.TOKEN); + break; + case URI: + confSp.setType(SearchParamType.URI); + break; + case SPECIAL: + confSp.setType(SearchParamType.SPECIAL); + break; + case HAS: + // Shouldn't happen + break; } - + } - + } } @@ -161,7 +165,7 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S } massage(retVal); - + retVal.getImplementation().setDescription(myImplementationDescription); myCachedValue = retVal; return retVal; @@ -170,7 +174,11 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S public boolean isIncludeResourceCounts() { return myIncludeResourceCounts; } - + + public void setIncludeResourceCounts(boolean theIncludeResourceCounts) { + myIncludeResourceCounts = theIncludeResourceCounts; + } + /** * Subclasses may override */ @@ -187,10 +195,6 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S myImplementationDescription = theImplDesc; } - public void setIncludeResourceCounts(boolean theIncludeResourceCounts) { - myIncludeResourceCounts = theIncludeResourceCounts; - } - @Override public void setRestfulServer(RestfulServer theRestfulServer) { this.myRestfulServer = theRestfulServer; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java index 0196bb61c55..1d65424a338 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java @@ -51,16 +51,16 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S private boolean myIncludeResourceCounts; private RestfulServer myRestfulServer; private IFhirSystemDao mySystemDao; - + /** * Constructor */ @CoverageIgnore - public JpaConformanceProviderR5(){ + public JpaConformanceProviderR5() { super(); setIncludeResourceCounts(true); } - + /** * Constructor */ @@ -93,7 +93,7 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) { nextResource.setVersioning(ResourceVersionPolicy.VERSIONEDUPDATE); - + ConditionalDeleteStatus conditionalDelete = nextResource.getConditionalDelete(); if (conditionalDelete == ConditionalDeleteStatus.MULTIPLE && myDaoConfig.isAllowMultipleDelete() == false) { nextResource.setConditionalDelete(ConditionalDeleteStatus.SINGLE); @@ -108,7 +108,7 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S nextResource.getSearchParam().clear(); String resourceName = nextResource.getType(); RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); - Collection searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef); + Collection searchParams = mySearchParamRegistry.getSearchParamsByResourceType(resourceDef); for (RuntimeSearchParam runtimeSp : searchParams) { CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam(); @@ -116,37 +116,40 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S confSp.setDocumentation(runtimeSp.getDescription()); confSp.setDefinition(runtimeSp.getUri()); switch (runtimeSp.getParamType()) { - case COMPOSITE: - confSp.setType(SearchParamType.COMPOSITE); - break; - case DATE: - confSp.setType(SearchParamType.DATE); - break; - case NUMBER: - confSp.setType(SearchParamType.NUMBER); - break; - case QUANTITY: - confSp.setType(SearchParamType.QUANTITY); - break; - case REFERENCE: - confSp.setType(SearchParamType.REFERENCE); - break; - case STRING: - confSp.setType(SearchParamType.STRING); - break; - case TOKEN: - confSp.setType(SearchParamType.TOKEN); - break; - case URI: - confSp.setType(SearchParamType.URI); - break; - case HAS: - // Shouldn't happen - break; + case COMPOSITE: + confSp.setType(SearchParamType.COMPOSITE); + break; + case DATE: + confSp.setType(SearchParamType.DATE); + break; + case NUMBER: + confSp.setType(SearchParamType.NUMBER); + break; + case QUANTITY: + confSp.setType(SearchParamType.QUANTITY); + break; + case REFERENCE: + confSp.setType(SearchParamType.REFERENCE); + break; + case STRING: + confSp.setType(SearchParamType.STRING); + break; + case TOKEN: + confSp.setType(SearchParamType.TOKEN); + break; + case URI: + confSp.setType(SearchParamType.URI); + break; + case SPECIAL: + confSp.setType(SearchParamType.SPECIAL); + break; + case HAS: + // Shouldn't happen + break; } - + } - + } } @@ -160,7 +163,7 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S } massage(retVal); - + retVal.getImplementation().setDescription(myImplementationDescription); myCachedValue = retVal; return retVal; @@ -169,7 +172,11 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S public boolean isIncludeResourceCounts() { return myIncludeResourceCounts; } - + + public void setIncludeResourceCounts(boolean theIncludeResourceCounts) { + myIncludeResourceCounts = theIncludeResourceCounts; + } + /** * Subclasses may override */ @@ -186,10 +193,6 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S myImplementationDescription = theImplDesc; } - public void setIncludeResourceCounts(boolean theIncludeResourceCounts) { - myIncludeResourceCounts = theIncludeResourceCounts; - } - @Override public void setRestfulServer(RestfulServer theRestfulServer) { this.myRestfulServer = theRestfulServer; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java index 8ec617e6084..e6922fe75fb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java @@ -195,6 +195,11 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc { mySchedulerService.scheduleFixedDelay(10 * DateUtils.MILLIS_PER_SECOND, true, jobDetail); } + @VisibleForTesting + ReentrantLock getIndexingLockForUnitTest() { + return myIndexingLock; + } + @Override @Transactional(Transactional.TxType.NEVER) public Integer runReindexingPass() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java index 7cdf48c75c6..fe9b2d6eec9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java @@ -68,7 +68,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { private static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor; protected static Server ourServer; protected IGenericClient ourClient; - ResourceCountCache ourResourceCountsCache; + ResourceCountCache myResourceCountsCache; private TerminologyUploaderProvider myTerminologyUploaderProvider; private boolean ourRestHookSubscriptionInterceptorRequested; @@ -93,6 +93,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); myFhirCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); + myResourceCountsCache = (ResourceCountCache) myAppCtx.getBean("myResourceCountsCache"); if (ourServer == null) { ourRestServer = new RestfulServer(myFhirCtx); @@ -113,7 +114,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { ourRestServer.setServerConformanceProvider(confProvider); ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class); - ourResourceCountsCache = (ResourceCountCache) myAppCtx.getBean("myResourceCountsCache"); Server server = new Server(0); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java index d80dd679964..bb44312788c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java @@ -1,6 +1,9 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.util.ExtensionConstants; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; @@ -13,6 +16,7 @@ import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Patient; import org.junit.AfterClass; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -25,6 +29,29 @@ public class ServerR4Test extends BaseResourceProviderR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerR4Test.class); + @Autowired + private IFhirResourceDao myCapabilityStatementDao; + + @Test + public void testCapabilityStatementValidates() throws IOException { + HttpGet get = new HttpGet(ourServerBase + "/metadata?_pretty=true&_format=json"); + try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { + assertEquals(200, resp.getStatusLine().getStatusCode()); + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + + ourLog.info(respString); + + CapabilityStatement cs = myFhirCtx.newJsonParser().parseResource(CapabilityStatement.class, respString); + + try { + myCapabilityStatementDao.validate(cs, null, respString, EncodingEnum.JSON, null, null, null); + } catch (PreconditionFailedException e) { + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); + fail(); + } + } + } + /** * See #519 @@ -71,7 +98,7 @@ public class ServerR4Test extends BaseResourceProviderR4Test { * Initial fetch after a clear should return * no results */ - ourResourceCountsCache.clear(); + myResourceCountsCache.clear(); CapabilityStatement capabilityStatement = ourClient .capabilities() @@ -93,7 +120,7 @@ public class ServerR4Test extends BaseResourceProviderR4Test { * Now run a background pass (the update * method is called by the scheduler normally) */ - ourResourceCountsCache.update(); + myResourceCountsCache.update(); capabilityStatement = ourClient .capabilities() diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java new file mode 100644 index 00000000000..65fb4c99a8c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java @@ -0,0 +1,151 @@ +package ca.uhn.fhir.jpa.provider.r5; + +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.util.ExtensionConstants; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.hl7.fhir.r5.model.CapabilityStatement; +import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent; +import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; +import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.Patient; +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.*; + +public class ServerR5Test extends BaseResourceProviderR5Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerR5Test.class); + + @Autowired + private IFhirResourceDao myCapabilityStatementDao; + + @Test + @Ignore + public void testCapabilityStatementValidates() throws IOException { + HttpGet get = new HttpGet(ourServerBase + "/metadata?_pretty=true&_format=json"); + try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { + assertEquals(200, resp.getStatusLine().getStatusCode()); + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + + ourLog.info(respString); + + CapabilityStatement cs = myFhirCtx.newJsonParser().parseResource(CapabilityStatement.class, respString); + + try { + myCapabilityStatementDao.validate(cs, null, respString, EncodingEnum.JSON, null, null, null); + } catch (PreconditionFailedException e) { + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); + fail(); + } + } + } + + + /** + * See #519 + */ + @Test + public void saveIdParamOnlyAppearsOnce() throws IOException { + HttpGet get = new HttpGet(ourServerBase + "/metadata?_pretty=true&_format=xml"); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + ourLog.info(resp.toString()); + assertEquals(200, resp.getStatusLine().getStatusCode()); + + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(respString); + + CapabilityStatement cs = myFhirCtx.newXmlParser().parseResource(CapabilityStatement.class, respString); + + for (CapabilityStatementRestResourceComponent nextResource : cs.getRest().get(0).getResource()) { + ourLog.info("Testing resource: " + nextResource.getType()); + Set sps = new HashSet(); + for (CapabilityStatementRestResourceSearchParamComponent nextSp : nextResource.getSearchParam()) { + if (sps.add(nextSp.getName()) == false) { + fail("Duplicate search parameter " + nextSp.getName() + " for resource " + nextResource.getType()); + } + } + + if (!sps.contains("_id")) { + fail("No search parameter _id for resource " + nextResource.getType()); + } + } + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + } + + + @Test + public void testMetadataIncludesResourceCounts() { + Patient p = new Patient(); + p.setActive(true); + ourClient.create().resource(p).execute(); + + /* + * Initial fetch after a clear should return + * no results + */ + myResourceCountsCache.clear(); + + CapabilityStatement capabilityStatement = ourClient + .capabilities() + .ofType(CapabilityStatement.class) + .execute(); + + Extension patientCountExt = capabilityStatement + .getRest() + .get(0) + .getResource() + .stream() + .filter(t -> t.getType().equals("Patient")) + .findFirst() + .orElseThrow(() -> new InternalErrorException("No patient")) + .getExtensionByUrl(ExtensionConstants.CONF_RESOURCE_COUNT); + assertNull(patientCountExt); + + /* + * Now run a background pass (the update + * method is called by the scheduler normally) + */ + myResourceCountsCache.update(); + + capabilityStatement = ourClient + .capabilities() + .ofType(CapabilityStatement.class) + .execute(); + + patientCountExt = capabilityStatement + .getRest() + .get(0) + .getResource() + .stream() + .filter(t -> t.getType().equals("Patient")) + .findFirst() + .orElseThrow(() -> new InternalErrorException("No patient")) + .getExtensionByUrl(ExtensionConstants.CONF_RESOURCE_COUNT); + assertEquals("1", patientCountExt.getValueAsPrimitive().getValueAsString()); + + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java index 615ecb143ce..b1d61b6cbd3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java @@ -26,6 +26,8 @@ import org.springframework.data.domain.SliceImpl; import org.springframework.transaction.PlatformTransactionManager; import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -94,6 +96,17 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest { mySvc.start(); } + @Test + public void testNoParallelReindexing() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + new Thread(()->{ + mySvc.getIndexingLockForUnitTest().lock(); + latch.countDown(); + }).start(); + latch.await(10, TimeUnit.SECONDS); + mySvc.runReindexingPass(); + } + @Test public void testReindexPassOnlyReturnsValuesAtLowThreshold() { mockNothingToExpunge(); diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java index 41c732105ec..73d83e9f963 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/ServerCapabilityStatementProvider.java @@ -9,7 +9,10 @@ import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.*; +import ca.uhn.fhir.rest.server.Bindings; +import ca.uhn.fhir.rest.server.IServerConformanceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.RestfulServerConfiguration; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.method.*; import ca.uhn.fhir.rest.server.method.SearchParameter; @@ -113,7 +116,6 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState } - private DateTimeType conformanceDate(RequestDetails theRequestDetails) { IPrimitiveType buildDate = getServerConfiguration(theRequestDetails).getConformanceDate(); if (buildDate != null && buildDate.getValue() != null) { @@ -127,7 +129,6 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState } - /** * Gets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted. @@ -194,47 +195,46 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState // Map nameToSearchParam = new HashMap(); for (BaseMethodBinding nextMethodBinding : nextEntry.getValue()) { - if (nextMethodBinding.getRestOperationType() != null) { - String resOpCode = nextMethodBinding.getRestOperationType().getCode(); - if (resOpCode != null) { - TypeRestfulInteraction resOp; - try { - resOp = TypeRestfulInteraction.fromCode(resOpCode); - } catch (Exception e) { - resOp = null; + nextMethodBinding.getRestOperationType(); + String resOpCode = nextMethodBinding.getRestOperationType().getCode(); + if (resOpCode != null) { + TypeRestfulInteraction resOp; + try { + resOp = TypeRestfulInteraction.fromCode(resOpCode); + } catch (Exception e) { + resOp = null; + } + if (resOp != null) { + if (resourceOps.contains(resOp) == false) { + resourceOps.add(resOp); + resource.addInteraction().setCode(resOp); } - if (resOp != null) { + if ("vread".equals(resOpCode)) { + // vread implies read + resOp = TypeRestfulInteraction.READ; if (resourceOps.contains(resOp) == false) { resourceOps.add(resOp); resource.addInteraction().setCode(resOp); } - if ("vread".equals(resOpCode)) { - // vread implies read - resOp = TypeRestfulInteraction.READ; - if (resourceOps.contains(resOp) == false) { - resourceOps.add(resOp); - resource.addInteraction().setCode(resOp); - } - } + } - if (nextMethodBinding.isSupportsConditional()) { - switch (resOp) { - case CREATE: - resource.setConditionalCreate(true); - break; - case DELETE: - if (nextMethodBinding.isSupportsConditionalMultiple()) { - resource.setConditionalDelete(ConditionalDeleteStatus.MULTIPLE); - } else { - resource.setConditionalDelete(ConditionalDeleteStatus.SINGLE); - } - break; - case UPDATE: - resource.setConditionalUpdate(true); - break; - default: - break; - } + if (nextMethodBinding.isSupportsConditional()) { + switch (resOp) { + case CREATE: + resource.setConditionalCreate(true); + break; + case DELETE: + if (nextMethodBinding.isSupportsConditionalMultiple()) { + resource.setConditionalDelete(ConditionalDeleteStatus.MULTIPLE); + } else { + resource.setConditionalDelete(ConditionalDeleteStatus.SINGLE); + } + break; + case UPDATE: + resource.setConditionalUpdate(true); + break; + default: + break; } } } @@ -315,28 +315,18 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState } sortSearchParameters(searchParameters); if (!searchParameters.isEmpty()) { - // boolean allOptional = searchParameters.get(0).isRequired() == false; - // - // OperationDefinition query = null; - // if (!allOptional) { - // RestOperation operation = rest.addOperation(); - // query = new OperationDefinition(); - // operation.setDefinition(new ResourceReferenceDt(query)); - // query.getDescriptionElement().setValue(searchMethodBinding.getDescription()); - // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName)); - // for (String nextInclude : searchMethodBinding.getIncludes()) { - // query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude)); - // } - // } for (SearchParameter nextParameter : searchParameters) { + if (nextParameter.getParamType() == null) { + ourLog.warn("SearchParameter {}:{} does not declare a type - Not exporting in CapabilityStatement", def.getName(), nextParameter.getName()); + continue; + } + String nextParamName = nextParameter.getName(); - String chain = null; String nextParamUnchainedName = nextParamName; if (nextParamName.contains(".")) { - chain = nextParamName.substring(nextParamName.indexOf('.') + 1); nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.')); } @@ -352,45 +342,18 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState } } + CapabilityStatementRestResourceSearchParamComponent param = resource.addSearchParam(); + String typeCode = nextParameter.getParamType().getCode(); + param.getTypeElement().setValueAsString(typeCode); param.setName(nextParamUnchainedName); - -// if (StringUtils.isNotBlank(chain)) { -// param.addChain(chain); -// } -// -// if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { -// for (String nextWhitelist : new TreeSet(nextParameter.getQualifierWhitelist())) { -// if (nextWhitelist.startsWith(".")) { -// param.addChain(nextWhitelist.substring(1)); -// } -// } -// } - param.setDocumentation(nextParamDescription); - if (nextParameter.getParamType() != null) { - param.getTypeElement().setValueAsString(nextParameter.getParamType().getCode()); - } - for (Class nextTarget : nextParameter.getDeclaredTypes()) { - RuntimeResourceDefinition targetDef = getServerConfiguration(theRequestDetails).getFhirContext().getResourceDefinition(nextTarget); - if (targetDef != null) { - ResourceType code; - try { - code = ResourceType.fromCode(targetDef.getName()); - } catch (FHIRException e) { - code = null; - } -// if (code != null) { -// param.addTarget(targetDef.getName()); -// } - } - } + } } } - @Read(type = OperationDefinition.class) public OperationDefinition readOperationDefinition(@IdParam IdType theId, RequestDetails theRequestDetails) { if (theId == null || theId.hasIdPart() == false) { diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9e16885771c..ca77f971450 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -476,6 +476,10 @@ Search parameters of type URI did not work in the hapi-fhir-testpage-overlay. This has been corrected. + + JPA servers accidentally stripped the type attribute from the server-exported CapabilityStatement + when search parameters of type "special" were found. This has been corrected. + From 9d4df3e470c96cffd24bff45c92f36f455ed05a7 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 30 Oct 2019 05:39:31 -0400 Subject: [PATCH 04/10] Add a test --- .../rest/server/method/ReadMethodBinding.java | 3 +-- .../server/method/ReadMethodBindingTest.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java index 3bc0a03380e..b384ced6587 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java @@ -140,8 +140,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding { if (Constants.PARAM_HISTORY.equals(theRequest.getOperation())) { if (mySupportsVersion == false) { return false; - } - if (theRequest.getId().hasVersionIdPart() == false) { + } else if (theRequest.getId().hasVersionIdPart() == false) { return false; } } else if (!StringUtils.isBlank(theRequest.getOperation())) { diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java index adeb846f749..598dddf84aa 100644 --- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java @@ -101,6 +101,31 @@ public class ReadMethodBindingTest { when(myRequestDetails.getOperation()).thenReturn("$foo"); assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + // History operation + when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123")); + when(myRequestDetails.getOperation()).thenReturn("_history"); + assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + + } + + @Test + public void testIncomingServerRequestNoMatch_HasCompartment() throws NoSuchMethodException { + + class MyProvider { + @Read(version = false) + public IBaseResource read(@IdParam IIdType theIdType) { + return null; + } + } + + when(myCtx.getResourceDefinition(any(Class.class))).thenReturn(definition); + when(definition.getName()).thenReturn("Patient"); + when(myRequestDetails.getResourceName()).thenReturn("Patient"); + when(myRequestDetails.getCompartmentName()).thenReturn("Patient"); + when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123")); + + ReadMethodBinding binding = createBinding(new MyProvider()); + assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); } public ReadMethodBinding createBinding(Object theProvider) throws NoSuchMethodException { From 007cfaf00ef3f63dbf67e8f1494eb449053e6d37 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 30 Oct 2019 08:38:39 -0400 Subject: [PATCH 05/10] Don't error out on missing CS (#1570) * Don't error out on mi9ssing CS * Add tests * Add a bit of test coverage * One more test --- .../ca/uhn/fhir/i18n/hapi-messages.properties | 1 + .../fhir/jpa/term/BaseTermReadSvcImpl.java | 5 +- .../jpa/term/IValueSetConceptAccumulator.java | 2 + .../jpa/term/ValueSetConceptAccumulator.java | 10 ++- ...ansionComponentWithConceptAccumulator.java | 9 +++ .../r5/FhirResourceDaoR5SearchNoFtTest.java | 39 ++++++++++ .../term/ValueSetConceptAccumulatorTest.java | 75 +++++++++++++++++++ .../jpa/term/ValueSetExpansionR4Test.java | 17 +++++ .../uhn/fhir/jpa/model/util/JpaConstants.java | 13 ++++ .../module/cache/SubscriptionLoaderTest.java | 14 +++- 10 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulatorTest.java diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index b2071d8b18a..62f9b35e864 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -1,4 +1,5 @@ +ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionRefersToUnknownCs=Unknown CodeSystem URI "{0}" referenced from ValueSet # Core Library Messages ca.uhn.fhir.context.FhirContext.unknownResourceName=Unknown resource name "{0}" (this name is not known in FHIR version "{1}") 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 f8f30f77378..de6fc3a1a65 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 @@ -675,7 +675,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo CodeSystem codeSystemFromContext = getCodeSystemFromContext(system); if (codeSystemFromContext == null) { - throw new InvalidRequestException("Unknown code system: " + system); + String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionRefersToUnknownCs", system); + ourLog.warn(msg); + theValueSetCodeAccumulator.addMessage(msg); + return false; } if (!theIncludeOrExclude.getConcept().isEmpty()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java index 729deb3b317..ef9f60244ae 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java @@ -27,6 +27,8 @@ import java.util.Collection; public interface IValueSetConceptAccumulator { + void addMessage(String theMessage); + void includeConcept(String theSystem, String theCode, String theDisplay); void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection theDesignations); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java index 55a8d2e5b8f..00ca0ef7c5a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java @@ -57,6 +57,12 @@ public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator { myConceptsExcluded = 0; } + @Override + public void addMessage(String theMessage) { + // ignore for now + + } + @Override public void includeConcept(String theSystem, String theCode, String theDisplay) { saveConcept(theSystem, theCode, theDisplay); @@ -82,7 +88,7 @@ public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator { if (optionalConcept.isPresent()) { TermValueSetConcept concept = optionalConcept.get(); - ourLog.info("Excluding [{}|{}] from ValueSet[{}]", concept.getSystem(), concept.getCode(), myTermValueSet.getUrl()); + ourLog.debug("Excluding [{}|{}] from ValueSet[{}]", concept.getSystem(), concept.getCode(), myTermValueSet.getUrl()); for (TermValueSetConceptDesignation designation : concept.getDesignations()) { myValueSetConceptDesignationDao.deleteById(designation.getId()); myTermValueSet.decrementTotalConceptDesignations(); @@ -90,7 +96,7 @@ public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator { myValueSetConceptDao.deleteById(concept.getId()); myTermValueSet.decrementTotalConcepts(); myValueSetDao.save(myTermValueSet); - ourLog.info("Done excluding [{}|{}] from ValueSet[{}]", concept.getSystem(), concept.getCode(), myTermValueSet.getUrl()); + ourLog.debug("Done excluding [{}|{}] from ValueSet[{}]", concept.getSystem(), concept.getCode(), myTermValueSet.getUrl()); if (++myConceptsExcluded % 250 == 0) { ourLog.info("Have excluded {} concepts from ValueSet[{}]", myConceptsExcluded, myTermValueSet.getUrl()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java index 146b1fecb2d..dd764a189cf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java @@ -22,9 +22,11 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.TermConceptDesignation; +import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.model.api.annotation.Block; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.ValueSet; import javax.annotation.Nullable; @@ -54,6 +56,13 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V return myMaxCapacity - myConceptsCount; } + @Override + public void addMessage(String theMessage) { + addExtension() + .setUrl(JpaConstants.EXT_VALUESET_EXPANSION_MESSAGE) + .setValue(new StringType(theMessage)); + } + @Override public void includeConcept(String theSystem, String theCode, String theDisplay) { incrementConceptsCount(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5SearchNoFtTest.java index 143a279982a..362152f540f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5SearchNoFtTest.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.dao.r5; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.api.Constants; @@ -9,11 +11,14 @@ import ca.uhn.fhir.rest.param.HasOrListParam; import ca.uhn.fhir.rest.param.HasParam; import ca.uhn.fhir.rest.param.StringParam; import org.hl7.fhir.r5.model.Organization; +import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Practitioner; import org.hl7.fhir.r5.model.PractitionerRole; import org.junit.AfterClass; import org.junit.Test; +import java.util.Date; + import static org.junit.Assert.assertEquals; @SuppressWarnings({"unchecked", "Duplicates"}) @@ -98,6 +103,40 @@ public class FhirResourceDaoR5SearchNoFtTest extends BaseJpaR5Test { assertEquals(1, outcome.getResources(0, 1).size()); } + @Test + public void testSearchDoesntFailIfResourcesAreDeleted() { + + Patient p = new Patient(); + p.addIdentifier().setValue("1"); + myPatientDao.create(p); + + p = new Patient(); + p.addIdentifier().setValue("2"); + myPatientDao.create(p); + + p = new Patient(); + p.addIdentifier().setValue("3"); + Long id = myPatientDao.create(p).getId().getIdPartAsLong(); + + IBundleProvider outcome = myPatientDao.search(new SearchParameterMap()); + assertEquals(3, outcome.size().intValue()); + + runInTransaction(()->{ + ResourceTable table = myResourceTableDao.findById(id).orElseThrow(() -> new IllegalArgumentException()); + table.setDeleted(new Date()); + myResourceTableDao.save(table); + }); + + assertEquals(2, outcome.getResources(0, 3).size()); + + runInTransaction(()->{ + myResourceHistoryTableDao.deleteAll(); + }); + + assertEquals(0, outcome.getResources(0, 3).size()); + } + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulatorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulatorTest.java new file mode 100644 index 00000000000..e29aba98053 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulatorTest.java @@ -0,0 +1,75 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDesignationDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao; +import ca.uhn.fhir.jpa.entity.TermValueSet; +import ca.uhn.fhir.jpa.entity.TermValueSetConcept; +import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; +import org.hl7.fhir.r4.model.ValueSet; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Optional; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class ValueSetConceptAccumulatorTest { + + private ValueSetConceptAccumulator myAccumulator; + private TermValueSet myValueSet; + @Mock + private ITermValueSetDao myValueSetDao; + @Mock + private ITermValueSetConceptDesignationDao myValueSetDesignationDao; + @Mock + private ITermValueSetConceptDao myValueSetConceptDao; + + @Before + public void before() { + myValueSet = new TermValueSet(); + myAccumulator = new ValueSetConceptAccumulator(myValueSet, myValueSetDao, myValueSetConceptDao, myValueSetDesignationDao); + } + + @Test + public void testIncludeConcept() { + for (int i = 0; i < 1000; i++) { + myAccumulator.includeConcept("sys", "code", "display"); + } + verify(myValueSetConceptDao, times(1000)).save(any()); + } + + @Test + public void testExcludeBlankConcept() { + myAccumulator.excludeConcept("", ""); + verifyNoInteractions(myValueSetConceptDao); + } + + @Test + public void testAddMessage() { + myAccumulator.addMessage("foo"); + verifyNoInteractions(myValueSetConceptDao); + } + + @Test + public void testExcludeConceptWithDesignations() { + for (int i = 0; i <1000; i++) { + + TermValueSetConcept value = new TermValueSetConcept(); + value.setCode("code"); + value.getDesignations().add(new TermValueSetConceptDesignation().setValue("foo")); + + when(myValueSetConceptDao.findByTermValueSetIdSystemAndCode(any(), eq("sys"), eq("code"+i))).thenReturn(Optional.of(value)); + + myAccumulator.excludeConcept("sys", "code"+i); + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java index 40ab5130434..560a1116d37 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java @@ -2,11 +2,13 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import com.google.common.collect.Lists; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.codesystems.HttpVerb; import org.junit.Test; @@ -688,6 +690,21 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { assertEquals("Systolische bloeddruk minimaal 1 uur", designationComponent.getValue()); } + @Test + public void testExpandValueSetWithUnknownCodeSystem() { + ValueSet vs = new ValueSet(); + ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem("http://unknown-system"); + ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null); + assertEquals(0, outcome.getExpansion().getContains().size()); + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(encoded); + + Extension extensionByUrl = outcome.getExpansion().getExtensionByUrl(JpaConstants.EXT_VALUESET_EXPANSION_MESSAGE); + assertEquals("Unknown CodeSystem URI \"http://unknown-system\" referenced from ValueSet", extensionByUrl.getValueAsPrimitive().getValueAsString()); + } + + @Test public void testExpandTermValueSetAndChildrenWithOffsetAndCountWithClientAssignedId() throws Exception { myDaoConfig.setPreExpandValueSets(true); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java index 95962a2badd..73a08451e68 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java @@ -24,6 +24,13 @@ import ca.uhn.fhir.rest.api.Constants; public class JpaConstants { + /** + * Non-instantiable + */ + private JpaConstants() { + // nothing + } + /** * Operation name for the $apply-codesystem-delta-add operation */ @@ -243,6 +250,12 @@ public class JpaConstants { */ public static final String EXTENSION_EXT_SYSTEMDEFINED = JpaConstants.class.getName() + "_EXTENSION_EXT_SYSTEMDEFINED"; + /** + * Message added to expansion valueset + */ + public static final String EXT_VALUESET_EXPANSION_MESSAGE = "http://hapifhir.io/fhir/StructureDefinition/valueset-expansion-message"; + + /** * Parameter for the $export operation */ diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java index 04cbcaafd6e..f7e3e55ad72 100755 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java @@ -11,6 +11,8 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; @@ -50,7 +52,17 @@ public class SubscriptionLoaderTest extends BaseBlockingQueueSubscribableChannel @Test public void testMultipleThreadsDontBlock() throws InterruptedException { SubscriptionLoader svc = new SubscriptionLoader(); - svc.acquireSemaphoreForUnitTest(); + CountDownLatch latch = new CountDownLatch(1); + new Thread(()->{ + try { + svc.acquireSemaphoreForUnitTest(); + latch.countDown(); + } catch (InterruptedException theE) { + // ignore + } + }).start(); + + latch.await(10, TimeUnit.SECONDS); svc.syncSubscriptions(); } From 16cfcc87c8660dfc292ade71032eb983b2360948 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 30 Oct 2019 08:57:51 -0400 Subject: [PATCH 06/10] Work on extractor --- .../extractor/BaseSearchParamExtractor.java | 102 +++++++++++------- .../extractor/ISearchParamExtractor.java | 40 +++++-- 2 files changed, 92 insertions(+), 50 deletions(-) diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index f2e8397ac5a..d69582b1ce5 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -59,6 +59,15 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor @Autowired private ModelConfig myModelConfig; private Set myIgnoredForSearchDatatypes; + private BaseRuntimeChildDefinition quantityValueChild; + private BaseRuntimeChildDefinition quantitySystemChild; + private BaseRuntimeChildDefinition quantityCodeChild; + private BaseRuntimeChildDefinition moneyValueChild; + private BaseRuntimeChildDefinition moneyCurrencyChild; + private BaseRuntimeElementCompositeDefinition locationPositionDefinition; + private BaseRuntimeChildDefinition codeSystemUrlValueChild; + private BaseRuntimeChildDefinition rangeLowValueChild; + private BaseRuntimeChildDefinition rangeHighValueChild; /** * Constructor @@ -77,14 +86,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public Set extractSearchParamTokens(IBaseResource theResource) { - BaseRuntimeElementCompositeDefinition codeSystemDefinition; - BaseRuntimeChildDefinition codeSystemUrlValueChild = null; - if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { - codeSystemDefinition = getContext().getResourceDefinition("CodeSystem"); - assert codeSystemDefinition != null; - codeSystemUrlValueChild = codeSystemDefinition.getChildByName("url"); - } + public SearchParamSet extractSearchParamTokens(IBaseResource theResource) { String resourceTypeName = toRootTypeName(theResource); String useSystem; @@ -176,15 +178,20 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor addToken_ContactPoint(resourceTypeName, params, searchParam, value); break; default: - throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + addUnexpectedDatatypeWarning(params, searchParam, value); + break; } }; return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN); } + public void addUnexpectedDatatypeWarning(SearchParamSet params, RuntimeSearchParam searchParam, IBase value) { + params.addWarning("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + } + @Override - public Set extractSearchParamUri(IBaseResource theResource) { + public SearchParamSet extractSearchParamUri(IBaseResource theResource) { IExtractor extractor = (params, searchParam, value, path) -> { String nextType = toRootTypeName(value); String resourceType = toRootTypeName(theResource); @@ -197,7 +204,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor addUri_Uri(resourceType, params, searchParam, value); break; default: - throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + addUnexpectedDatatypeWarning(params, searchParam, value); + break; } }; @@ -205,13 +213,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public Set extractSearchParamCoords(IBaseResource theResource) { - // TODO: implement - return Collections.emptySet(); + public SearchParamSet extractSearchParamCoords(IBaseResource theResource) { + return new SearchParamSet<>(); } @Override - public Set extractSearchParamDates(IBaseResource theResource) { + public SearchParamSet extractSearchParamDates(IBaseResource theResource) { IExtractor extractor = (params, searchParam, value, path) -> { String nextType = toRootTypeName(value); String resourceType = toRootTypeName(theResource); @@ -231,7 +238,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor // CarePlan.activitydate can be a string - ignored for now break; default: - throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + addUnexpectedDatatypeWarning(params, searchParam, value); + break; } }; @@ -239,7 +247,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public Set extractSearchParamNumber(IBaseResource theResource) { + public SearchParamSet extractSearchParamNumber(IBaseResource theResource) { IExtractor extractor = (params, searchParam, value, path) -> { String nextType = toRootTypeName(value); @@ -260,7 +268,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor addNumber_Decimal(resourceType, params, searchParam, value); break; default: - throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + addUnexpectedDatatypeWarning(params, searchParam, value); + break; } }; @@ -268,11 +277,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public Set extractSearchParamQuantity(IBaseResource theResource) { - BaseRuntimeElementCompositeDefinition locationDefinition = (BaseRuntimeElementCompositeDefinition) getContext().getResourceDefinition("Location"); - BaseRuntimeChildDefinition locationPositionValueChild = locationDefinition.getChildByName("position"); - BaseRuntimeElementCompositeDefinition locationPositionDefinition = (BaseRuntimeElementCompositeDefinition) locationPositionValueChild.getChildByName("position"); - + public SearchParamSet extractSearchParamQuantity(IBaseResource theResource) { IExtractor extractor = (params, searchParam, value, path) -> { if (value.getClass().equals(locationPositionDefinition.getImplementingClass())) { @@ -293,7 +298,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor addQuantity_Range(resourceType, params, searchParam, value); break; default: - throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + addUnexpectedDatatypeWarning(params, searchParam, value); + break; } }; @@ -301,7 +307,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } @Override - public Set extractSearchParamStrings(IBaseResource theResource) { + public SearchParamSet extractSearchParamStrings(IBaseResource theResource) { IExtractor extractor = (params, searchParam, value, path) -> { String resourceType = toRootTypeName(theResource); @@ -330,7 +336,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor addString_Range(resourceType, params, searchParam, value); break; default: - throw new ConfigurationException("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + addUnexpectedDatatypeWarning(params, searchParam, value); + break; } }; @@ -356,7 +363,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } /** - * [ * Override parent because we're using FHIRPath here */ private List extractValues(String thePaths, IBaseResource theResource) { @@ -419,13 +425,34 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor addIgnoredType(getContext(), "Ratio", myIgnoredForSearchDatatypes); addIgnoredType(getContext(), "SampledData", myIgnoredForSearchDatatypes); addIgnoredType(getContext(), "Signature", myIgnoredForSearchDatatypes); + + BaseRuntimeElementCompositeDefinition quantityDefinition = (BaseRuntimeElementCompositeDefinition) getContext().getElementDefinition("Quantity"); + quantityValueChild = quantityDefinition.getChildByName("value"); + quantitySystemChild = quantityDefinition.getChildByName("system"); + quantityCodeChild = quantityDefinition.getChildByName("code"); + + BaseRuntimeElementCompositeDefinition moneyDefinition = (BaseRuntimeElementCompositeDefinition) getContext().getElementDefinition("Money"); + moneyValueChild = moneyDefinition.getChildByName("value"); + moneyCurrencyChild = moneyDefinition.getChildByName("currency"); + + BaseRuntimeElementCompositeDefinition locationDefinition = getContext().getResourceDefinition("Location"); + BaseRuntimeChildDefinition locationPositionValueChild = locationDefinition.getChildByName("position"); + locationPositionDefinition = (BaseRuntimeElementCompositeDefinition) locationPositionValueChild.getChildByName("position"); + + BaseRuntimeElementCompositeDefinition codeSystemDefinition; + if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { + codeSystemDefinition = getContext().getResourceDefinition("CodeSystem"); + assert codeSystemDefinition != null; + codeSystemUrlValueChild = codeSystemDefinition.getChildByName("url"); + } + + BaseRuntimeElementCompositeDefinition rangeDefinition = (BaseRuntimeElementCompositeDefinition) getContext().getElementDefinition("Range"); + rangeLowValueChild = rangeDefinition.getChildByName("low"); + rangeHighValueChild = rangeDefinition.getChildByName("high"); + } private void addQuantity_Quantity(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) { - BaseRuntimeElementCompositeDefinition quantityDefinition = (BaseRuntimeElementCompositeDefinition) getContext().getElementDefinition("Quantity"); - BaseRuntimeChildDefinition quantityValueChild = quantityDefinition.getChildByName("value"); - BaseRuntimeChildDefinition quantitySystemChild = quantityDefinition.getChildByName("system"); - BaseRuntimeChildDefinition quantityCodeChild = quantityDefinition.getChildByName("code"); Optional> valueField = quantityValueChild.getAccessor().getFirstValueOrNull(theValue); if (valueField.isPresent() && valueField.get().getValue() != null) { @@ -440,9 +467,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } private void addQuantity_Money(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) { - BaseRuntimeElementCompositeDefinition moneyDefinition = (BaseRuntimeElementCompositeDefinition) getContext().getElementDefinition("Money"); - BaseRuntimeChildDefinition moneyValueChild = moneyDefinition.getChildByName("value"); - BaseRuntimeChildDefinition moneyCurrencyChild = moneyDefinition.getChildByName("currency"); Optional> valueField = moneyValueChild.getAccessor().getFirstValueOrNull(theValue); if (valueField.isPresent() && valueField.get().getValue() != null) { @@ -458,9 +482,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } private void addQuantity_Range(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) { - BaseRuntimeElementCompositeDefinition rangeDefinition = (BaseRuntimeElementCompositeDefinition) getContext().getElementDefinition("Range"); - BaseRuntimeChildDefinition rangeLowValueChild = rangeDefinition.getChildByName("low"); - BaseRuntimeChildDefinition rangeHighValueChild = rangeDefinition.getChildByName("high"); Optional low = rangeLowValueChild.getAccessor().getFirstValueOrNull(theValue); low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase)); @@ -758,8 +779,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } - private Set extractSearchParams(IBaseResource theResource, IExtractor theExtractor, RestSearchParameterTypeEnum theSearchParamType) { - Set retVal = new HashSet<>(); + private SearchParamSet extractSearchParams(IBaseResource theResource, IExtractor theExtractor, RestSearchParameterTypeEnum theSearchParamType) { + SearchParamSet retVal = new SearchParamSet<>(); Collection searchParams = getSearchParams(theResource); for (RuntimeSearchParam nextSpDef : searchParams) { @@ -879,14 +900,13 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor List get() throws FHIRException; - } @FunctionalInterface private interface IExtractor { - void extract(Set theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath); + void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java index 16e3d9b9d3e..7d929d53ef5 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java @@ -4,8 +4,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.model.entity.*; import org.hl7.fhir.instance.model.api.IBaseResource; -import java.util.List; -import java.util.Set; +import java.util.*; /* * #%L @@ -29,21 +28,44 @@ import java.util.Set; public interface ISearchParamExtractor { - Set extractSearchParamCoords(IBaseResource theResource); + SearchParamSet extractSearchParamCoords(IBaseResource theResource); - Set extractSearchParamDates(IBaseResource theResource); + SearchParamSet extractSearchParamDates(IBaseResource theResource); - Set extractSearchParamNumber(IBaseResource theResource); + SearchParamSet extractSearchParamNumber(IBaseResource theResource); - Set extractSearchParamQuantity(IBaseResource theResource); + SearchParamSet extractSearchParamQuantity(IBaseResource theResource); - Set extractSearchParamStrings(IBaseResource theResource); + SearchParamSet extractSearchParamStrings(IBaseResource theResource); - Set extractSearchParamTokens(IBaseResource theResource); + SearchParamSet extractSearchParamTokens(IBaseResource theResource); - Set extractSearchParamUri(IBaseResource theResource); + SearchParamSet extractSearchParamUri(IBaseResource theResource); List extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef); String[] split(String theExpression); + + + class SearchParamSet extends HashSet { + + private List myWarnings; + + public void addWarning(String theWarning) { + if (myWarnings == null) { + myWarnings = new ArrayList<>(); + } + myWarnings.add(theWarning); + } + + List getWarnings() { + if (myWarnings == null) { + return Collections.emptyList(); + } + return myWarnings; + } + + } + + } From c8801f1977dae683b30c22015e2e3e3aa9acc139 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 30 Oct 2019 09:08:13 -0400 Subject: [PATCH 07/10] Fix public erver --- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 349420e0e62..ae6a33b1f43 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -51,7 +51,6 @@ hapi-fhir-testpage-overlay ${project.version} classes - provided From d3e66680f099459e56a7ecef0497a2a37aa75912 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 30 Oct 2019 09:49:40 -0400 Subject: [PATCH 08/10] Add some testing --- hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml | 1 - .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 2 +- ...rchParamWithInlineReferencesExtractor.java | 4 +- hapi-fhir-jpaserver-searchparam/pom.xml | 5 + .../extractor/BaseSearchParamExtractor.java | 364 +++++++++--------- .../SearchParamExtractorService.java | 78 +++- .../matcher/IndexedSearchParamExtractor.java | 2 +- .../SearchParamExtractorDstu3Test.java | 62 +-- .../SearchParamExtractorServiceTest.java | 44 +++ 9 files changed, 326 insertions(+), 236 deletions(-) create mode 100644 hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorServiceTest.java diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml index c360b7279ee..563d57b9e23 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml @@ -45,7 +45,6 @@ hapi-fhir-testpage-overlay ${project.version} classes - provided