From 0729e38e6ec5e472a0e3c87062623358d323dcc6 Mon Sep 17 00:00:00 2001 From: Diederik Muylwyk Date: Thu, 11 Jul 2019 16:52:19 -0400 Subject: [PATCH] Resolve "experimental implementation for storing expanded ValueSets in terminology tables" (#1369) * Added experimental implementation for storing expanded ValueSets in terminology tables. * Minor tweak to log message for consistency. * Another minor tweak to log message for consistency. * Renamed test. * Addressing review comments. * Added migration tasks. --- .../java/ca/uhn/fhir/util/ValidateUtil.java | 16 +- .../ca/uhn/fhir/i18n/hapi-messages.properties | 3 +- .../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 32 ++++ .../fhir/jpa/dao/data/ITermCodeSystemDao.java | 2 +- .../jpa/dao/data/ITermValueSetCodeDao.java | 35 ++++ .../fhir/jpa/dao/data/ITermValueSetDao.java | 43 +++++ .../dstu3/FhirResourceDaoValueSetDstu3.java | 34 +++- .../dao/expunge/ExpungeEverythingService.java | 2 + .../jpa/dao/r4/FhirResourceDaoValueSetR4.java | 62 +++++-- .../uhn/fhir/jpa/entity/TermCodeSystem.java | 28 ++- .../jpa/entity/TermCodeSystemVersion.java | 19 +- .../ca/uhn/fhir/jpa/entity/TermConcept.java | 77 ++++---- .../jpa/entity/TermConceptDesignation.java | 34 +++- .../uhn/fhir/jpa/entity/TermConceptMap.java | 47 +++-- .../fhir/jpa/entity/TermConceptMapGroup.java | 47 +++-- .../entity/TermConceptMapGroupElement.java | 35 ++-- .../TermConceptMapGroupElementTarget.java | 39 ++-- .../entity/TermConceptParentChildLink.java | 26 +-- .../fhir/jpa/entity/TermConceptProperty.java | 43 +++-- .../ca/uhn/fhir/jpa/entity/TermValueSet.java | 144 +++++++++++++++ .../uhn/fhir/jpa/entity/TermValueSetCode.java | 172 ++++++++++++++++++ .../jpa/term/BaseHapiTerminologySvcImpl.java | 102 ++++++++++- .../fhir/jpa/term/IHapiTerminologySvc.java | 4 + .../FhirResourceDaoDstu3TerminologyTest.java | 49 ++--- .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 8 + .../r4/FhirResourceDaoR4TerminologyTest.java | 2 +- .../dao/r4/FhirResourceDaoR4ValueSetTest.java | 30 +-- .../jpa/term/TerminologySvcImplDstu3Test.java | 2 +- .../jpa/term/TerminologySvcImplR4Test.java | 126 ++++++++++++- .../tasks/HapiFhirJpaMigrationTasks.java | 35 ++++ 30 files changed, 1080 insertions(+), 218 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java index 6b5df86e696..8d234d690bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java @@ -23,7 +23,7 @@ package ca.uhn.fhir.util; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.*; public class ValidateUtil { @@ -37,7 +37,7 @@ public class ValidateUtil { } /** - * Throws {@link IllegalArgumentException} if theValue is <= theMinimum + * Throws {@link IllegalArgumentException} if theValue is < theMinimum */ public static void isGreaterThanOrEqualTo(long theValue, long theMinimum, String theMessage) { if (theValue < theMinimum) { @@ -45,6 +45,12 @@ public class ValidateUtil { } } + public static void isNotBlankOrThrowIllegalArgument(String theString, String theMessage) { + if (isBlank(theString)) { + throw new IllegalArgumentException(theMessage); + } + } + public static void isNotBlankOrThrowInvalidRequest(String theString, String theMessage) { if (isBlank(theString)) { throw new InvalidRequestException(theMessage); @@ -57,6 +63,12 @@ public class ValidateUtil { } } + public static void isNotTooLongOrThrowIllegalArgument(String theString, int theMaxLength, String theMessage) { + if (length(theString) > theMaxLength) { + throw new IllegalArgumentException(theMessage); + } + } + public static void isTrueOrThrowInvalidRequest(boolean theSuccess, String theMessage) { if (theSuccess == false) { throw new InvalidRequestException(theMessage); 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 5838eb9baf1..45d5426bda2 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 @@ -115,8 +115,9 @@ ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascadi ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation +ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1} -ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUri=Can not create multiple code systems with URI "{0}", already have one with resource ID: {1} +ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 1a149a9134e..d8df260f116 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -145,6 +145,10 @@ public class DaoConfig { private boolean myEnableInMemorySubscriptionMatching = true; private boolean myEnforceReferenceTargetTypes = true; private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC; + /** + * EXPERIMENTAL - Do not use in production! Do not change default of {@code false}! + */ + private boolean myPreExpandValueSetsExperimental = false; /** * Constructor @@ -1600,6 +1604,34 @@ public class DaoConfig { myModelConfig.setWebsocketContextPath(theWebsocketContextPath); } + /** + * EXPERIMENTAL - Do not use in production! + *

+ * If set to {@code true}, ValueSets and expansions are stored in terminology tables. This is to facilitate + * future optimization of the $expand operation on large ValueSets. + *

+ *

+ * The default value for this setting is {@code false}. + *

+ */ + public boolean isPreExpandValueSetsExperimental() { + return myPreExpandValueSetsExperimental; + } + + /** + * EXPERIMENTAL - Do not use in production! + *

+ * If set to {@code true}, ValueSets and expansions are stored in terminology tables. This is to facilitate + * future optimization of the $expand operation on large ValueSets. + *

+ *

+ * The default value for this setting is {@code false}. + *

+ */ + public void setPreExpandValueSetsExperimental(boolean thePreExpandValueSetsExperimental) { + myPreExpandValueSetsExperimental = thePreExpandValueSetsExperimental; + } + public enum IndexEnabledEnum { ENABLED, DISABLED diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java index 5fe695bc06c..3a672f8a312 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java @@ -34,7 +34,7 @@ public interface ITermCodeSystemDao extends JpaRepository TermCodeSystem findByCodeSystemUri(@Param("code_system_uri") String theCodeSystemUri); @Query("SELECT cs FROM TermCodeSystem cs WHERE cs.myResourcePid = :resource_pid") - TermCodeSystem findByResourcePid(@Param("resource_pid") Long theReourcePid); + TermCodeSystem findByResourcePid(@Param("resource_pid") Long theResourcePid); @Query("SELECT cs FROM TermCodeSystem cs WHERE cs.myCurrentVersion.myId = :csv_pid") Optional findWithCodeSystemVersionAsCurrentVersion(@Param("csv_pid") Long theCodeSystemVersionPid); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java new file mode 100644 index 00000000000..5702aceb846 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.jpa.entity.TermValueSetCode; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ITermValueSetCodeDao extends JpaRepository { + + @Query("DELETE FROM TermValueSetCode vsc WHERE vsc.myValueSet.myId = :pid") + @Modifying + void deleteTermValueSetCodesByValueSetId(@Param("pid") Long theValueSetId); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java new file mode 100644 index 00000000000..087dcefe70e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.jpa.entity.TermValueSet; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface ITermValueSetDao extends JpaRepository { + + @Query("DELETE FROM TermValueSet vs WHERE vs.myId = :pid") + @Modifying + void deleteTermValueSetById(@Param("pid") Long theId); + + @Query("SELECT vs FROM TermValueSet vs WHERE vs.myResourcePid = :resource_pid") + Optional findByResourcePid(@Param("resource_pid") Long theResourcePid); + + @Query("SELECT vs FROM TermValueSet vs WHERE vs.myUrl = :url") + Optional findByUrl(@Param("url") String theUrl); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java index 93116549405..2f83a634c66 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java @@ -23,11 +23,15 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ElementUtil; import org.apache.commons.codec.binary.StringUtils; +import org.hl7.fhir.convertors.VersionConvertor_30_40; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.*; @@ -37,6 +41,8 @@ import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.slf4j.Logger; @@ -45,14 +51,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import java.util.Collections; +import java.util.Date; import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 implements IFhirResourceDaoValueSet { - private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoValueSetDstu3.class); + + @Autowired + private IHapiTerminologySvc myHapiTerminologySvc; + @Autowired @Qualifier("myJpaValidationSupportChainDstu3") private IValidationSupport myValidationSupport; @@ -295,4 +305,26 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 // nothing } + @Override + protected 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 (myDaoConfig.isPreExpandValueSetsExperimental()) { + if (retVal.getDeleted() == null) { + try { + ValueSet valueSet = (ValueSet) theResource; + org.hl7.fhir.r4.model.ValueSet converted = VersionConvertor_30_40.convertValueSet(valueSet); + myHapiTerminologySvc.storeTermValueSetAndChildren(retVal, converted); + } catch (FHIRException fe) { + throw new InternalErrorException(fe); + } + } else { + myHapiTerminologySvc.deleteValueSetAndChildren(retVal); + } + } + + return retVal; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java index b704bb0668e..feaf1a820a9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java @@ -74,6 +74,8 @@ public class ExpungeEverythingService { counter.addAndGet(doExpungeEverythingQuery(txTemplate, ResourceLink.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, SearchResult.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, SearchInclude.class)); + counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermValueSetCode.class)); + counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermValueSet.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermConceptParentChildLink.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermConceptMapGroupElementTarget.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermConceptMapGroupElement.class)); 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 c1c685ceb00..68e78e778db 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 @@ -20,34 +20,43 @@ package ca.uhn.fhir.jpa.dao.r4; * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.Collections; -import java.util.List; - -import org.apache.commons.codec.binary.StringUtils; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; -import org.hl7.fhir.r4.model.ValueSet.*; -import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ElementUtil; +import org.apache.commons.codec.binary.StringUtils; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; +import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; +import org.hl7.fhir.r4.model.ValueSet.FilterOperator; +import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4 implements IFhirResourceDaoValueSet { + @Autowired + private IHapiTerminologySvc myHapiTerminologySvc; + @Autowired @Qualifier("myJpaValidationSupportChainR4") private IValidationSupport myValidationSupport; @@ -296,4 +305,21 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4 imple // nothing } + @Override + protected 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 (myDaoConfig.isPreExpandValueSetsExperimental()) { + if (retVal.getDeleted() == null) { + ValueSet valueSet = (ValueSet) theResource; + myHapiTerminologySvc.storeTermValueSetAndChildren(retVal, valueSet); + } else { + myHapiTerminologySvc.deleteValueSetAndChildren(retVal); + } + } + + return retVal; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java index fd23f3f4065..6d468b49ae6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java @@ -21,13 +21,16 @@ package ca.uhn.fhir.jpa.entity; */ import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; //@formatter:off @Table(name = "TRM_CODESYSTEM", uniqueConstraints = { @@ -37,9 +40,11 @@ import static org.apache.commons.lang3.StringUtils.left; //@formatter:on public class TermCodeSystem implements Serializable { private static final long serialVersionUID = 1L; - public static final int CS_NAME_LENGTH = 200; - @Column(name = "CODE_SYSTEM_URI", nullable = false) + private static final int MAX_NAME_LENGTH = 200; + static final int MAX_URL_LENGTH = 200; + + @Column(name = "CODE_SYSTEM_URI", nullable = false, length = MAX_URL_LENGTH) private String myCodeSystemUri; @OneToOne() @@ -55,7 +60,7 @@ public class TermCodeSystem implements Serializable { private ResourceTable myResource; @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResourcePid; - @Column(name = "CS_NAME", nullable = true) + @Column(name = "CS_NAME", nullable = true, length = MAX_NAME_LENGTH) private String myName; public String getCodeSystemUri() { @@ -66,16 +71,21 @@ public class TermCodeSystem implements Serializable { return myName; } - public void setCodeSystemUri(String theCodeSystemUri) { + public TermCodeSystem setCodeSystemUri(@Nonnull String theCodeSystemUri) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCodeSystemUri, "theCodeSystemUri must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCodeSystemUri, MAX_URL_LENGTH, + "URI exceeds maximum length (" + MAX_URL_LENGTH + "): " + length(theCodeSystemUri)); myCodeSystemUri = theCodeSystemUri; + return this; } public TermCodeSystemVersion getCurrentVersion() { return myCurrentVersion; } - public void setCurrentVersion(TermCodeSystemVersion theCurrentVersion) { + public TermCodeSystem setCurrentVersion(TermCodeSystemVersion theCurrentVersion) { myCurrentVersion = theCurrentVersion; + return this; } public Long getPid() { @@ -86,12 +96,14 @@ public class TermCodeSystem implements Serializable { return myResource; } - public void setName(String theName) { - myName = left(theName, CS_NAME_LENGTH); + public TermCodeSystem setName(String theName) { + myName = left(theName, MAX_NAME_LENGTH); + return this; } - public void setResource(ResourceTable theResource) { + public TermCodeSystem setResource(ResourceTable theResource) { myResource = theResource; + return this; } @Override 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 2bd272cb6a5..969710901ea 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 @@ -22,12 +22,15 @@ package ca.uhn.fhir.jpa.entity; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.util.CoverageIgnore; +import ca.uhn.fhir.util.ValidateUtil; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import static org.apache.commons.lang3.StringUtils.length; + //@formatter:off @Table(name = "TRM_CODESYSTEM_VER" // Note, we used to have a constraint named IDX_CSV_RESOURCEPID_AND_VER (don't reuse this) @@ -37,6 +40,8 @@ import java.util.Collection; public class TermCodeSystemVersion implements Serializable { private static final long serialVersionUID = 1L; + static final int MAX_VERSION_LENGTH = 200; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem") private Collection myConcepts; @@ -50,7 +55,7 @@ public class TermCodeSystemVersion implements Serializable { @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CODESYSVER_RES_ID")) private ResourceTable myResource; - @Column(name = "CS_VERSION_ID", nullable = true, updatable = false) + @Column(name = "CS_VERSION_ID", nullable = true, updatable = false, length = MAX_VERSION_LENGTH) private String myCodeSystemVersionId; /** * This was added in HAPI FHIR 3.3.0 and is nullable just to avoid migration @@ -104,16 +109,21 @@ public class TermCodeSystemVersion implements Serializable { return myCodeSystem; } - public void setCodeSystem(TermCodeSystem theCodeSystem) { + public TermCodeSystemVersion setCodeSystem(TermCodeSystem theCodeSystem) { myCodeSystem = theCodeSystem; + return this; } public String getCodeSystemVersionId() { return myCodeSystemVersionId; } - public void setCodeSystemVersionId(String theCodeSystemVersionId) { + public TermCodeSystemVersion setCodeSystemVersionId(String theCodeSystemVersionId) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument( + theCodeSystemVersionId, MAX_VERSION_LENGTH, + "Version ID exceeds maximum length (" + MAX_VERSION_LENGTH + "): " + length(theCodeSystemVersionId)); myCodeSystemVersionId = theCodeSystemVersionId; + return this; } public Collection getConcepts() { @@ -131,8 +141,9 @@ public class TermCodeSystemVersion implements Serializable { return myResource; } - public void setResource(ResourceTable theResource) { + public TermCodeSystemVersion setResource(ResourceTable theResource) { myResource = theResource; + return this; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index bfe8ee61f53..3a3158cc4bc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -1,25 +1,5 @@ package ca.uhn.fhir.jpa.entity; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; -import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; -import ca.uhn.fhir.util.ValidateUtil; -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; -import org.hibernate.search.annotations.*; -import org.hl7.fhir.r4.model.Coding; - -import javax.annotation.Nonnull; -import javax.persistence.*; -import javax.persistence.Index; -import java.io.Serializable; -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; - /* * #%L * HAPI FHIR JPA Server @@ -40,6 +20,27 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ +import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; +import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.hibernate.search.annotations.*; +import org.hl7.fhir.r4.model.Coding; + +import javax.annotation.Nonnull; +import javax.persistence.Index; +import javax.persistence.*; +import java.io.Serializable; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Indexed(interceptor = DeferConceptIndexingInterceptor.class) @Table(name = "TRM_CONCEPT", uniqueConstraints = { @@ -49,16 +50,17 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED") }) public class TermConcept implements Serializable { - public static final int CODE_LENGTH = 500; - protected static final int MAX_DESC_LENGTH = 400; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class); private static final long serialVersionUID = 1L; + static final int MAX_CODE_LENGTH = 500; + static final int MAX_DESC_LENGTH = 400; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {}) private Collection myChildren; - @Column(name = "CODE", length = CODE_LENGTH, nullable = false) + @Column(name = "CODE", nullable = false, length = MAX_CODE_LENGTH) @Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),}) private String myCode; @Temporal(TemporalType.TIMESTAMP) @@ -70,7 +72,7 @@ public class TermConcept implements Serializable { @Column(name = "CODESYSTEM_PID", insertable = false, updatable = false) @Fields({@Field(name = "myCodeSystemVersionPid")}) private long myCodeSystemVersionPid; - @Column(name = "DISPLAY", length = MAX_DESC_LENGTH, nullable = true) + @Column(name = "DISPLAY", nullable = true, length = MAX_DESC_LENGTH) @Fields({ @Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")), @Field(name = "myDisplayEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")), @@ -187,20 +189,24 @@ public class TermConcept implements Serializable { return myCode; } - public void setCode(String theCode) { - ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "Code must not be null or empty"); + public TermConcept setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, MAX_CODE_LENGTH, + "Code exceeds maximum length (" + MAX_CODE_LENGTH + "): " + length(theCode)); myCode = theCode; + return this; } public TermCodeSystemVersion getCodeSystemVersion() { return myCodeSystem; } - public void setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { + public TermConcept setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { myCodeSystem = theCodeSystemVersion; if (theCodeSystemVersion.getPid() != null) { myCodeSystemVersionPid = theCodeSystemVersion.getPid(); } + return this; } public List getCodingProperties(String thePropertyName) { @@ -231,10 +237,7 @@ public class TermConcept implements Serializable { } public TermConcept setDisplay(String theDisplay) { - myDisplay = theDisplay; - if (isNotBlank(theDisplay) && theDisplay.length() > MAX_DESC_LENGTH) { - myDisplay = myDisplay.substring(0, MAX_DESC_LENGTH); - } + myDisplay = left(theDisplay, MAX_DESC_LENGTH); return this; } @@ -246,8 +249,9 @@ public class TermConcept implements Serializable { return myIndexStatus; } - public void setIndexStatus(Long theIndexStatus) { + public TermConcept setIndexStatus(Long theIndexStatus) { myIndexStatus = theIndexStatus; + return this; } public String getParentPidsAsString() { @@ -272,8 +276,9 @@ public class TermConcept implements Serializable { return mySequence; } - public void setSequence(Integer theSequence) { + public TermConcept setSequence(Integer theSequence) { mySequence = theSequence; + return this; } public List getStringProperties(String thePropertyName) { @@ -300,8 +305,9 @@ public class TermConcept implements Serializable { return myUpdated; } - public void setUpdated(Date theUpdated) { + public TermConcept setUpdated(Date theUpdated) { myUpdated = theUpdated; + return this; } @Override @@ -355,8 +361,9 @@ public class TermConcept implements Serializable { myParentPids = b.toString(); } - public void setParentPids(String theParentPids) { + public TermConcept setParentPids(String theParentPids) { myParentPids = theParentPids; + return this; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java index 9741d4377b4..4aeea98ccae 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java @@ -20,16 +20,24 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import ca.uhn.fhir.util.ValidateUtil; + +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_DESIG", uniqueConstraints = { }, indexes = { }) public class TermConceptDesignation implements Serializable { - private static final long serialVersionUID = 1L; + + private static final int MAX_LENGTH = 500; + @ManyToOne @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT")) private TermConcept myConcept; @@ -38,15 +46,15 @@ public class TermConceptDesignation implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_DESIG_PID") @Column(name = "PID") private Long myId; - @Column(name = "LANG", length = 500, nullable = true) + @Column(name = "LANG", nullable = true, length = MAX_LENGTH) private String myLanguage; - @Column(name = "USE_SYSTEM", length = 500, nullable = true) + @Column(name = "USE_SYSTEM", nullable = true, length = MAX_LENGTH) private String myUseSystem; - @Column(name = "USE_CODE", length = 500, nullable = true) + @Column(name = "USE_CODE", nullable = true, length = MAX_LENGTH) private String myUseCode; - @Column(name = "USE_DISPLAY", length = 500, nullable = true) + @Column(name = "USE_DISPLAY", nullable = true, length = MAX_LENGTH) private String myUseDisplay; - @Column(name = "VAL", length = 500, nullable = false) + @Column(name = "VAL", nullable = false, length = MAX_LENGTH) private String myValue; /** * TODO: Make this non-null @@ -62,6 +70,8 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setLanguage(String theLanguage) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theLanguage, MAX_LENGTH, + "Language exceeds maximum length (" + MAX_LENGTH + "): " + length(theLanguage)); myLanguage = theLanguage; return this; } @@ -71,6 +81,8 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setUseCode(String theUseCode) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseCode, MAX_LENGTH, + "Use code exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseCode)); myUseCode = theUseCode; return this; } @@ -80,7 +92,7 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setUseDisplay(String theUseDisplay) { - myUseDisplay = theUseDisplay; + myUseDisplay = left(theUseDisplay, MAX_LENGTH); return this; } @@ -89,6 +101,9 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setUseSystem(String theUseSystem) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseSystem, MAX_LENGTH, + "Use system exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseSystem)); + myUseSystem = theUseSystem; return this; } @@ -97,7 +112,10 @@ public class TermConceptDesignation implements Serializable { return myValue; } - public TermConceptDesignation setValue(String theValue) { + public TermConceptDesignation setValue(@Nonnull String theValue) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theValue, "theValue must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theValue, MAX_LENGTH, + "Value exceeds maximum length (" + MAX_LENGTH + "): " + length(theValue)); myValue = theValue; return this; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java index fd67f26716c..24e1dda2797 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java @@ -21,19 +21,27 @@ package ca.uhn.fhir.jpa.entity; */ import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP", uniqueConstraints = { @UniqueConstraint(name = "IDX_CONCEPT_MAP_URL", columnNames = {"URL"}) }) public class TermConceptMap implements Serializable { + private static final long serialVersionUID = 1L; + + static final int MAX_URL_LENGTH = 200; + @Id() @SequenceGenerator(name = "SEQ_CONCEPT_MAP_PID", sequenceName = "SEQ_CONCEPT_MAP_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_MAP_PID") @@ -47,13 +55,13 @@ public class TermConceptMap implements Serializable { @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResourcePid; - @Column(name = "SOURCE_URL", nullable = true, length = 200) + @Column(name = "SOURCE_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String mySource; - @Column(name = "TARGET_URL", nullable = true, length = 200) + @Column(name = "TARGET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myTarget; - @Column(name = "URL", length = 200, nullable = false) + @Column(name = "URL", nullable = false, length = MAX_URL_LENGTH) private String myUrl; @OneToMany(mappedBy = "myConceptMap") @@ -75,47 +83,58 @@ public class TermConceptMap implements Serializable { return myResource; } - public void setResource(ResourceTable resource) { - myResource = resource; + public TermConceptMap setResource(ResourceTable theResource) { + myResource = theResource; + return this; } public Long getResourcePid() { return myResourcePid; } - public void setResourcePid(Long resourcePid) { - myResourcePid = resourcePid; + public TermConceptMap setResourcePid(Long theResourcePid) { + myResourcePid = theResourcePid; + return this; } public String getSource() { return mySource; } - public void setSource(String source) { - mySource = source; + public TermConceptMap setSource(String theSource) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSource, TermValueSet.MAX_URL_LENGTH, + "Source exceeds maximum length (" + TermValueSet.MAX_URL_LENGTH + "): " + length(theSource)); + mySource = theSource; + return this; } public String getTarget() { return myTarget; } - public void setTarget(String target) { - myTarget = target; + public TermConceptMap setTarget(String theTarget) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theTarget, TermValueSet.MAX_URL_LENGTH, + "Target exceeds maximum length (" + TermValueSet.MAX_URL_LENGTH + "): " + length(theTarget)); + myTarget = theTarget; + return this; } public String getUrl() { return myUrl; } - public void setUrl(String theUrl) { + public TermConceptMap setUrl(@Nonnull String theUrl) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theUrl, "theUrl must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUrl, MAX_URL_LENGTH, + "URL exceeds maximum length (" + MAX_URL_LENGTH + "): " + length(theUrl)); myUrl = theUrl; + return this; } @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) - .append("myResource", myResource.toString()) .append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)")) .append("myResourcePid", myResourcePid) .append("mySource", mySource) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java index 72d23359a0b..d11ae915f46 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java @@ -20,17 +20,23 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP_GROUP") public class TermConceptMapGroup implements Serializable { + private static final long serialVersionUID = 1L; + @Id() @SequenceGenerator(name = "SEQ_CONCEPT_MAP_GROUP_PID", sequenceName = "SEQ_CONCEPT_MAP_GROUP_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_MAP_GROUP_PID") @@ -41,36 +47,37 @@ public class TermConceptMapGroup implements Serializable { @JoinColumn(name = "CONCEPT_MAP_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGROUP_CONCEPTMAP")) private TermConceptMap myConceptMap; - @Column(name = "SOURCE_URL", nullable = false, length = 200) + @Column(name = "SOURCE_URL", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH) private String mySource; - @Column(name = "SOURCE_VERSION", length = 100) + @Column(name = "SOURCE_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String mySourceVersion; - @Column(name = "TARGET_URL", nullable = false, length = 200) + @Column(name = "TARGET_URL", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH) private String myTarget; - @Column(name = "TARGET_VERSION", length = 100) + @Column(name = "TARGET_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String myTargetVersion; @OneToMany(mappedBy = "myConceptMapGroup") private List myConceptMapGroupElements; - @Column(name= "CONCEPT_MAP_URL", length = 200, nullable = true) + @Column(name= "CONCEPT_MAP_URL", nullable = true, length = TermConceptMap.MAX_URL_LENGTH) private String myConceptMapUrl; - @Column(name= "SOURCE_VS", length = 200, nullable = true) + @Column(name= "SOURCE_VS", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String mySourceValueSet; - @Column(name= "TARGET_VS", length = 200, nullable = true) + @Column(name= "TARGET_VS", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myTargetValueSet; public TermConceptMap getConceptMap() { return myConceptMap; } - public void setConceptMap(TermConceptMap theTermConceptMap) { + public TermConceptMapGroup setConceptMap(TermConceptMap theTermConceptMap) { myConceptMap = theTermConceptMap; + return this; } public List getConceptMapGroupElements() { @@ -96,8 +103,12 @@ public class TermConceptMapGroup implements Serializable { return mySource; } - public void setSource(String theSource) { + public TermConceptMapGroup setSource(@Nonnull String theSource) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theSource, "theSource must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSource, TermCodeSystem.MAX_URL_LENGTH, + "Source exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theSource)); this.mySource = theSource; + return this; } public String getSourceValueSet() { @@ -111,16 +122,23 @@ public class TermConceptMapGroup implements Serializable { return mySourceVersion; } - public void setSourceVersion(String theSourceVersion) { + public TermConceptMapGroup setSourceVersion(String theSourceVersion) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSourceVersion, TermCodeSystemVersion.MAX_VERSION_LENGTH, + "Source version ID exceeds maximum length (" + TermCodeSystemVersion.MAX_VERSION_LENGTH + "): " + length(theSourceVersion)); mySourceVersion = theSourceVersion; + return this; } public String getTarget() { return myTarget; } - public void setTarget(String theTarget) { + public TermConceptMapGroup setTarget(@Nonnull String theTarget) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theTarget, "theTarget must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theTarget, TermCodeSystem.MAX_URL_LENGTH, + "Target exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theTarget)); this.myTarget = theTarget; + return this; } public String getTargetValueSet() { @@ -134,13 +152,16 @@ public class TermConceptMapGroup implements Serializable { return myTargetVersion; } - public void setTargetVersion(String theTargetVersion) { + public TermConceptMapGroup setTargetVersion(String theTargetVersion) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theTargetVersion, TermCodeSystemVersion.MAX_VERSION_LENGTH, + "Target version ID exceeds maximum length (" + TermCodeSystemVersion.MAX_VERSION_LENGTH + "): " + length(theTargetVersion)); myTargetVersion = theTargetVersion; + return this; } @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) .append(myConceptMap != null ? ("myConceptMap - id=" + myConceptMap.getId()) : ("myConceptMap=(null)")) .append("mySource", mySource) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java index d36689129ae..f182b16165e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java @@ -20,22 +20,28 @@ package ca.uhn.fhir.jpa.entity; * #L% */ -import org.apache.commons.lang3.Validate; +import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP_GRP_ELEMENT", indexes = { @Index(name = "IDX_CNCPT_MAP_GRP_CD", columnList = "SOURCE_CODE") }) public class TermConceptMapGroupElement implements Serializable { + private static final long serialVersionUID = 1L; + @Id() @SequenceGenerator(name = "SEQ_CONCEPT_MAP_GRP_ELM_PID", sequenceName = "SEQ_CONCEPT_MAP_GRP_ELM_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_MAP_GRP_ELM_PID") @@ -46,7 +52,7 @@ public class TermConceptMapGroupElement implements Serializable { @JoinColumn(name = "CONCEPT_MAP_GROUP_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGELEMENT_GROUP")) private TermConceptMapGroup myConceptMapGroup; - @Column(name = "SOURCE_CODE", nullable = false, length = TermConcept.CODE_LENGTH) + @Column(name = "SOURCE_CODE", nullable = false, length = TermConcept.MAX_CODE_LENGTH) private String myCode; @Column(name = "SOURCE_DISPLAY", length = TermConcept.MAX_DESC_LENGTH) @@ -55,33 +61,37 @@ public class TermConceptMapGroupElement implements Serializable { @OneToMany(mappedBy = "myConceptMapGroupElement") private List myConceptMapGroupElementTargets; - @Column(name = "CONCEPT_MAP_URL", length = 200) + @Column(name = "CONCEPT_MAP_URL", nullable = true, length = TermConceptMap.MAX_URL_LENGTH) private String myConceptMapUrl; - @Column(name = "SYSTEM_URL", length = 200) + @Column(name = "SYSTEM_URL", nullable = true, length = TermCodeSystem.MAX_URL_LENGTH) private String mySystem; - @Column(name = "SYSTEM_VERSION", length = 200) + @Column(name = "SYSTEM_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String mySystemVersion; - @Column(name = "VALUESET_URL", length = 200) + @Column(name = "VALUESET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myValueSet; public String getCode() { return myCode; } - public void setCode(String theCode) { - Validate.notBlank(theCode, "theCode must not be blank"); + public TermConceptMapGroupElement setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, TermConcept.MAX_CODE_LENGTH, + "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); myCode = theCode; + return this; } public TermConceptMapGroup getConceptMapGroup() { return myConceptMapGroup; } - public void setConceptMapGroup(TermConceptMapGroup theTermConceptMapGroup) { + public TermConceptMapGroupElement setConceptMapGroup(TermConceptMapGroup theTermConceptMapGroup) { myConceptMapGroup = theTermConceptMapGroup; + return this; } public List getConceptMapGroupElementTargets() { @@ -103,8 +113,9 @@ public class TermConceptMapGroupElement implements Serializable { return myDisplay; } - public void setDisplay(String theDisplay) { - myDisplay = theDisplay; + public TermConceptMapGroupElement setDisplay(String theDisplay) { + myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH); + return this; } public Long getId() { @@ -158,7 +169,7 @@ public class TermConceptMapGroupElement implements Serializable { @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) .append(myConceptMapGroup != null ? ("myConceptMapGroup - id=" + myConceptMapGroup.getId()) : ("myConceptMapGroup=(null)")) .append("myCode", myCode) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java index 754161ca0a9..d463f7dddfb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java @@ -20,20 +20,28 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP_GRP_ELM_TGT", indexes = { @Index(name = "IDX_CNCPT_MP_GRP_ELM_TGT_CD", columnList = "TARGET_CODE") }) public class TermConceptMapGroupElementTarget implements Serializable { + private static final long serialVersionUID = 1L; + + static final int MAX_EQUIVALENCE_LENGTH = 50; + @Id() @SequenceGenerator(name = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID", sequenceName = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID") @@ -44,31 +52,38 @@ public class TermConceptMapGroupElementTarget implements Serializable { @JoinColumn(name = "CONCEPT_MAP_GRP_ELM_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGETARGET_ELEMENT")) private TermConceptMapGroupElement myConceptMapGroupElement; - @Column(name = "TARGET_CODE", nullable = false, length = TermConcept.CODE_LENGTH) + @Column(name = "TARGET_CODE", nullable = false, length = TermConcept.MAX_CODE_LENGTH) private String myCode; - @Column(name = "TARGET_DISPLAY", length = TermConcept.MAX_DESC_LENGTH) + @Column(name = "TARGET_DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH) private String myDisplay; @Enumerated(EnumType.STRING) - @Column(name = "TARGET_EQUIVALENCE", length = 50) + @Column(name = "TARGET_EQUIVALENCE", nullable = true, length = MAX_EQUIVALENCE_LENGTH) private ConceptMapEquivalence myEquivalence; - @Column(name = "CONCEPT_MAP_URL", length = 200) + @Column(name = "CONCEPT_MAP_URL", nullable = true, length = TermConceptMap.MAX_URL_LENGTH) private String myConceptMapUrl; - @Column(name = "SYSTEM_URL", length = 200) + + @Column(name = "SYSTEM_URL", nullable = true, length = TermCodeSystem.MAX_URL_LENGTH) private String mySystem; - @Column(name = "SYSTEM_VERSION", length = 200) + + @Column(name = "SYSTEM_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String mySystemVersion; - @Column(name = "VALUESET_URL", length = 200) + + @Column(name = "VALUESET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myValueSet; public String getCode() { return myCode; } - public void setCode(String theCode) { + public TermConceptMapGroupElementTarget setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, TermConcept.MAX_CODE_LENGTH, + "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); myCode = theCode; + return this; } public TermConceptMapGroupElement getConceptMapGroupElement() { @@ -90,16 +105,18 @@ public class TermConceptMapGroupElementTarget implements Serializable { return myDisplay; } - public void setDisplay(String theDisplay) { + public TermConceptMapGroupElementTarget setDisplay(String theDisplay) { myDisplay = theDisplay; + return this; } public ConceptMapEquivalence getEquivalence() { return myEquivalence; } - public void setEquivalence(ConceptMapEquivalence theEquivalence) { + public TermConceptMapGroupElementTarget setEquivalence(ConceptMapEquivalence theEquivalence) { myEquivalence = theEquivalence; + return this; } public Long getId() { @@ -155,7 +172,7 @@ public class TermConceptMapGroupElementTarget implements Serializable { @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) .append(myConceptMapGroupElement != null ? ("myConceptMapGroupElement - id=" + myConceptMapGroupElement.getId()) : ("myConceptMapGroupElement=(null)")) .append("myCode", myCode) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java index dfb380d605e..c7d38eed131 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java @@ -20,21 +20,9 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import javax.persistence.*; import java.io.Serializable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -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.SequenceGenerator; -import javax.persistence.Table; - @Entity @Table(name = "TRM_CONCEPT_PC_LINK") public class TermConceptParentChildLink implements Serializable { @@ -136,20 +124,24 @@ public class TermConceptParentChildLink implements Serializable { return result; } - public void setChild(TermConcept theChild) { + public TermConceptParentChildLink setChild(TermConcept theChild) { myChild = theChild; + return this; } - public void setCodeSystem(TermCodeSystemVersion theCodeSystem) { + public TermConceptParentChildLink setCodeSystem(TermCodeSystemVersion theCodeSystem) { myCodeSystem = theCodeSystem; + return this; } - public void setParent(TermConcept theParent) { + public TermConceptParentChildLink setParent(TermConcept theParent) { myParent = theParent; + return this; } - public void setRelationshipType(RelationshipTypeEnum theRelationshipType) { + public TermConceptParentChildLink setRelationshipType(RelationshipTypeEnum theRelationshipType) { myRelationshipType = theRelationshipType; + return this; } public enum RelationshipTypeEnum { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java index 517dc1b70f1..144fc1ae632 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java @@ -20,21 +20,29 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hibernate.validator.constraints.NotBlank; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = { }, indexes = { }) public class TermConceptProperty implements Serializable { - - static final int MAX_PROPTYPE_ENUM_LENGTH = 6; private static final long serialVersionUID = 1L; + + private static final int MAX_LENGTH = 500; + static final int MAX_PROPTYPE_ENUM_LENGTH = 6; + @ManyToOne @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) private TermConcept myConcept; @@ -51,22 +59,22 @@ public class TermConceptProperty implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID") @Column(name = "PID") private Long myId; - @Column(name = "PROP_KEY", length = 500, nullable = false) + @Column(name = "PROP_KEY", nullable = false, length = MAX_LENGTH) @NotBlank private String myKey; - @Column(name = "PROP_VAL", length = 500, nullable = true) + @Column(name = "PROP_VAL", nullable = true, length = MAX_LENGTH) private String myValue; - @Column(name = "PROP_TYPE", length = MAX_PROPTYPE_ENUM_LENGTH, nullable = false) + @Column(name = "PROP_TYPE", nullable = false, length = MAX_PROPTYPE_ENUM_LENGTH) private TermConceptPropertyTypeEnum myType; /** * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ - @Column(name = "PROP_CODESYSTEM", length = 500, nullable = true) + @Column(name = "PROP_CODESYSTEM", length = MAX_LENGTH, nullable = true) private String myCodeSystem; /** * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ - @Column(name = "PROP_DISPLAY", length = 500, nullable = true) + @Column(name = "PROP_DISPLAY", length = MAX_LENGTH, nullable = true) private String myDisplay; /** @@ -80,6 +88,8 @@ public class TermConceptProperty implements Serializable { * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ public TermConceptProperty setCodeSystem(String theCodeSystem) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCodeSystem, MAX_LENGTH, + "Property code system exceeds maximum length (" + MAX_LENGTH + "): " + length(theCodeSystem)); myCodeSystem = theCodeSystem; return this; } @@ -95,7 +105,7 @@ public class TermConceptProperty implements Serializable { * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ public TermConceptProperty setDisplay(String theDisplay) { - myDisplay = theDisplay; + myDisplay = left(theDisplay, MAX_LENGTH); return this; } @@ -103,15 +113,20 @@ public class TermConceptProperty implements Serializable { return myKey; } - public void setKey(String theKey) { + public TermConceptProperty setKey(@Nonnull String theKey) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theKey, "theKey must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theKey, MAX_LENGTH, + "Code exceeds maximum length (" + MAX_LENGTH + "): " + length(theKey)); myKey = theKey; + return this; } public TermConceptPropertyTypeEnum getType() { return myType; } - public TermConceptProperty setType(TermConceptPropertyTypeEnum theType) { + public TermConceptProperty setType(@Nonnull TermConceptPropertyTypeEnum theType) { + Validate.notNull(theType); myType = theType; return this; } @@ -128,8 +143,9 @@ public class TermConceptProperty implements Serializable { * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string} * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property. */ - public void setValue(String theValue) { - myValue = theValue; + public TermConceptProperty setValue(String theValue) { + myValue = left(theValue, MAX_LENGTH); + return this; } public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { @@ -137,8 +153,9 @@ public class TermConceptProperty implements Serializable { return this; } - public void setConcept(TermConcept theConcept) { + public TermConceptProperty setConcept(TermConcept theConcept) { myConcept = theConcept; + return this; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java new file mode 100644 index 00000000000..d53d6d07191 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java @@ -0,0 +1,144 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.annotation.Nonnull; +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + +@Table(name = "TRM_VALUESET", uniqueConstraints = { + @UniqueConstraint(name = "IDX_VALUESET_URL", columnNames = {"URL"}) +}) +@Entity() +public class TermValueSet implements Serializable { + private static final long serialVersionUID = 1L; + + private static final int MAX_NAME_LENGTH = 200; + static final int MAX_URL_LENGTH = 200; + + @Id() + @SequenceGenerator(name = "SEQ_VALUESET_PID", sequenceName = "SEQ_VALUESET_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_PID") + @Column(name = "PID") + private Long myId; + + @Column(name = "URL", nullable = false, length = MAX_URL_LENGTH) + private String myUrl; + + @OneToOne() + @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_TRMVALUESET_RES")) + private ResourceTable myResource; + + @Column(name = "RES_ID", insertable = false, updatable = false) + private Long myResourcePid; + + @Column(name = "NAME", nullable = true, length = MAX_NAME_LENGTH) + private String myName; + + @OneToMany(mappedBy = "myValueSet") + private List myCodes; + + public Long getId() { + return myId; + } + + public String getUrl() { + return myUrl; + } + + public TermValueSet setUrl(@Nonnull String theUrl) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theUrl, "theUrl must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUrl, MAX_URL_LENGTH, + "URL exceeds maximum length (" + MAX_URL_LENGTH + "): " + length(theUrl)); + myUrl = theUrl; + return this; + } + + public ResourceTable getResource() { + return myResource; + } + + public TermValueSet setResource(ResourceTable theResource) { + myResource = theResource; + return this; + } + + public String getName() { + return myName; + } + + public TermValueSet setName(String theName) { + myName = left(theName, MAX_NAME_LENGTH); + return this; + } + + public List getCodes() { + if (myCodes == null) { + myCodes = new ArrayList<>(); + } + + return myCodes; + } + + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (!(theO instanceof TermValueSet)) return false; + + TermValueSet that = (TermValueSet) theO; + + return new EqualsBuilder() + .append(getUrl(), that.getUrl()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getUrl()) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("myId", myId) + .append("myUrl", myUrl) + .append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)")) + .append("myResourcePid", myResourcePid) + .append("myName", myName) + .append(myCodes != null ? ("myCodes - size=" + myCodes.size()) : ("myCodes=(null)")) + .toString(); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java new file mode 100644 index 00000000000..bbe0f518c35 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java @@ -0,0 +1,172 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.annotation.Nonnull; +import javax.persistence.*; +import java.io.Serializable; + +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + +@Table(name = "TRM_VALUESET_CODE", indexes = { + @Index(name = "IDX_VALUESET_CODE_CS_CD", columnList = "SYSTEM, CODE") +}) +@Entity() +public class TermValueSetCode implements Serializable { + private static final long serialVersionUID = 1L; + + @Id() + @SequenceGenerator(name = "SEQ_VALUESET_CODE_PID", sequenceName = "SEQ_VALUESET_CODE_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_CODE_PID") + @Column(name = "PID") + private Long myId; + + @ManyToOne() + @JoinColumn(name = "VALUESET_PID", referencedColumnName = "PID", nullable = false, foreignKey = @ForeignKey(name = "FK_TRM_VALUESET_PID")) + private TermValueSet myValueSet; + + @Transient + private String myValueSetUrl; + + @Transient + private String myValueSetName; + + @Column(name = "SYSTEM", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH) + private String mySystem; + + @Column(name = "CODE", nullable = false, length = TermConcept.MAX_CODE_LENGTH) + private String myCode; + + @Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH) + private String myDisplay; + + public Long getId() { + return myId; + } + + public TermValueSet getValueSet() { + return myValueSet; + } + + public TermValueSetCode setValueSet(TermValueSet theValueSet) { + myValueSet = theValueSet; + return this; + } + + public String getValueSetUrl() { + if (myValueSetUrl == null) { + myValueSetUrl = getValueSet().getUrl(); + } + + return myValueSetUrl; + } + + public String getValueSetName() { + if (myValueSetName == null) { + myValueSetName = getValueSet().getName(); + } + + return myValueSetName; + } + + public String getSystem() { + return mySystem; + } + + public TermValueSetCode setSystem(@Nonnull String theSystem) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theSystem, "theSystem must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSystem, TermCodeSystem.MAX_URL_LENGTH, + "System exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theSystem)); + + ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "theSystem must not be null or empty"); + if (theSystem.length() > TermCodeSystem.MAX_URL_LENGTH) { + throw new IllegalArgumentException(); + } + mySystem = theSystem; + return this; + } + + public String getCode() { + return myCode; + } + + public TermValueSetCode setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, TermConcept.MAX_CODE_LENGTH, + "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); + + myCode = theCode; + return this; + } + + public String getDisplay() { + return myDisplay; + } + + public TermValueSetCode setDisplay(String theDisplay) { + myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH); + return this; + } + + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (!(theO instanceof TermValueSetCode)) return false; + + TermValueSetCode that = (TermValueSetCode) theO; + + return new EqualsBuilder() + .append(getValueSetUrl(), that.getValueSetUrl()) + .append(getSystem(), that.getSystem()) + .append(getCode(), that.getCode()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getValueSetUrl()) + .append(getSystem()) + .append(getCode()) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("myId", myId) + .append(myValueSet != null ? ("myValueSet - id=" + myValueSet.getId()) : ("myValueSet=(null)")) + .append("myValueSetUrl", this.getValueSetUrl()) + .append("myValueSetName", this.getValueSetName()) + .append("mySystem", mySystem) + .append("myCode", myCode) + .append("myDisplay", myDisplay) + .toString(); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java index ba55a3712e9..165f1918d60 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java @@ -119,6 +119,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, @Autowired protected ITermConceptDesignationDao myConceptDesignationDao; @Autowired + protected ITermValueSetDao myValueSetDao; + @Autowired + protected ITermValueSetCodeDao myValueSetCodeDao; + @Autowired protected FhirContext myContext; @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; @@ -388,6 +392,31 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, deleteConceptMap(theResourceTable); } + public void deleteValueSet(ResourceTable theResourceTable) { + // Get existing entity so it can be deleted. + Optional optionalExistingTermValueSetById = myValueSetDao.findByResourcePid(theResourceTable.getId()); + + if (optionalExistingTermValueSetById.isPresent()) { + TermValueSet existingTermValueSet = optionalExistingTermValueSetById.get(); + + ourLog.info("Deleting existing TermValueSet {} and its children...", existingTermValueSet.getId()); + myValueSetCodeDao.deleteTermValueSetCodesByValueSetId(existingTermValueSet.getId()); + myValueSetDao.deleteTermValueSetById(existingTermValueSet.getId()); + ourLog.info("Done deleting existing TermValueSet {} and its children.", existingTermValueSet.getId()); + + ourLog.info("Flushing..."); + myValueSetCodeDao.flush(); + myValueSetDao.flush(); + ourLog.info("Done flushing."); + } + } + + @Override + @Transactional + public void deleteValueSetAndChildren(ResourceTable theResourceTable) { + deleteValueSet(theResourceTable); + } + private void doDelete(String theDescriptor, Supplier> theLoader, Supplier theCounter, JpaRepository theDao) { int count; ourLog.info(" * Deleting {}", theDescriptor); @@ -690,7 +719,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, private void expandWithoutHibernateSearch(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd, AtomicInteger theCodeCounter) { ourLog.trace("Hibernate search is not enabled"); - Validate.isTrue(theExpansionComponent.getParameter().isEmpty(), "Can not exapnd ValueSet with parameters - Hibernate Search is not enabled on this server."); + Validate.isTrue(theExpansionComponent.getParameter().isEmpty(), "Can not expand ValueSet with parameters - Hibernate Search is not enabled on this server."); Validate.isTrue(theInclude.getFilter().isEmpty(), "Can not expand ValueSet with filters - Hibernate Search is not enabled on this server."); Validate.isTrue(isNotBlank(theSystem), "Can not expand ValueSet without explicit system - Hibernate Search is not enabled on this server."); @@ -1144,7 +1173,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, myCodeSystemDao.save(codeSystem); } else { if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemVersion.getResource().getId())) { - String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUri", theSystemUri, + String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri, codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); throw new UnprocessableEntityException(msg); } @@ -1310,7 +1339,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, myConceptMapGroupElementTargetDao.save(termConceptMapGroupElementTarget); if (codesSaved++ % 250 == 0) { - ourLog.info("Have saved {} codes in conceptmap", codesSaved); + ourLog.info("Have saved {} codes in ConceptMap", codesSaved); myConceptMapGroupElementTargetDao.flush(); } } @@ -1334,6 +1363,69 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, ourLog.info("Done storing TermConceptMap."); } + @Override + @Transactional + public void storeTermValueSetAndChildren(ResourceTable theResourceTable, ValueSet theValueSet) { + ourLog.info("Storing TermValueSet {}", theValueSet.getIdElement().getValue()); + + ValidateUtil.isTrueOrThrowInvalidRequest(theResourceTable != null, "No resource supplied"); + ValidateUtil.isNotBlankOrThrowUnprocessableEntity(theValueSet.getUrl(), "ValueSet has no value for ValueSet.url"); + + TermValueSet termValueSet = new TermValueSet(); + termValueSet.setResource(theResourceTable); + termValueSet.setUrl(theValueSet.getUrl()); + termValueSet.setName(theValueSet.hasName() ? theValueSet.getName() : null); + + // We delete old versions; we don't support versioned ValueSets. + deleteValueSet(theResourceTable); + + /* + * Do the upload. + */ + String url = termValueSet.getUrl(); + Optional optionalExistingTermValueSetByUrl = myValueSetDao.findByUrl(url); + if (!optionalExistingTermValueSetByUrl.isPresent()) { + myValueSetDao.save(termValueSet); + int codesSaved = 0; + + ValueSet expandedValueSet = expandValueSet(theValueSet); + if (expandedValueSet.hasExpansion()) { + if (expandedValueSet.getExpansion().hasTotal() && expandedValueSet.getExpansion().getTotal() > 0) { + TermValueSetCode code; + for (ValueSet.ValueSetExpansionContainsComponent contains : expandedValueSet.getExpansion().getContains()) { + ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getSystem(), "ValueSet contains a code with no system value"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getCode(), "ValueSet contains a code with no code value"); + + code = new TermValueSetCode(); + code.setValueSet(termValueSet); + code.setSystem(contains.getSystem()); + code.setCode(contains.getCode()); + code.setDisplay(contains.hasDisplay() ? contains.getDisplay() : null); + myValueSetCodeDao.save(code); + + if (codesSaved++ % 250 == 0) { + ourLog.info("Have pre-expanded {} codes in ValueSet", codesSaved); + myValueSetCodeDao.flush(); + } + } + } + } + + } else { + TermValueSet existingTermValueSet = optionalExistingTermValueSetByUrl.get(); + + String msg = myContext.getLocalizer().getMessage( + BaseHapiTerminologySvcImpl.class, + "cannotCreateDuplicateValueSetUrl", + url, + existingTermValueSet.getResource().getIdDt().toUnqualifiedVersionless().getValue()); + + throw new UnprocessableEntityException(msg); + } + + ourLog.info("Done storing TermValueSet."); + } + @Override public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { VersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA); @@ -1581,9 +1673,9 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList theConceptsStack, IdentityHashMap theAllConcepts) { - ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() != null, "CodesystemValue is null"); + ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() != null, "CodeSystemVersion is null"); ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() == theCodeSystem, "CodeSystems are not equal"); - ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "Codesystem contains a code with no code value"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "CodeSystem contains a code with no code value"); if (theConceptsStack.contains(theConcept.getCode())) { throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java index cc861e27f7f..4d50f75afb5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java @@ -81,8 +81,12 @@ public interface IHapiTerminologySvc { void deleteConceptMapAndChildren(ResourceTable theResourceTable); + void deleteValueSetAndChildren(ResourceTable theResourceTable); + void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap); + void storeTermValueSetAndChildren(ResourceTable theResourceTable, ValueSet theValueSet); + boolean supportsSystem(String theCodeSystem); List translate(TranslationRequest theTranslationRequest); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java index b03e0b09148..64390e9a671 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java @@ -1,31 +1,13 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsStringIgnoringCase; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -import java.util.*; - -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory; -import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; -import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.dstu3.model.ValueSet.*; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; -import org.springframework.beans.factory.annotation.Autowired; - import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.model.entity.*; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.param.TokenParam; @@ -35,6 +17,25 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory; +import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; +import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; +import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { @@ -226,7 +227,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { myCodeSystemDao.create(codeSystem, mySrd); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); + assertEquals("Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); } } 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 e3d27011253..877d0601247 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 @@ -118,6 +118,10 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myCodeSystemDaoR4") protected IFhirResourceDaoCodeSystem myCodeSystemDao; @Autowired + protected ITermCodeSystemDao myTermCodeSystemDao; + @Autowired + protected ITermCodeSystemVersionDao myTermCodeSystemVersionDao; + @Autowired @Qualifier("myCompartmentDefinitionDaoR4") protected IFhirResourceDao myCompartmentDefinitionDao; @Autowired @@ -287,6 +291,10 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myValueSetDaoR4") protected IFhirResourceDaoValueSet myValueSetDao; @Autowired + protected ITermValueSetDao myTermValueSetDao; + @Autowired + protected ITermValueSetCodeDao myTermValueSetCodeDao; + @Autowired protected ITermConceptMapDao myTermConceptMapDao; @Autowired protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index 589f593c373..e362d462251 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -222,7 +222,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { myCodeSystemDao.create(codeSystem, mySrd); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); + assertEquals("Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java index 2a10ef2f9c6..cf3d7ce0bd6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java @@ -1,34 +1,20 @@ package ca.uhn.fhir.jpa.dao.r4; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; - -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.CodeType; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.UriType; -import org.hl7.fhir.r4.model.ValueSet; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.*; import org.junit.AfterClass; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; -import ca.uhn.fhir.util.TestUtil; +import java.io.IOException; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { @@ -224,7 +210,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { @Test - public void testValiedateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { + public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { IPrimitiveType display = null; Coding coding = null; CodeableConcept codeableConcept = null; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java index e7006342458..3089835220d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java @@ -177,7 +177,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs); fail(); } catch (UnprocessableEntityException e) { - assertThat(e.getMessage(), containsString("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/")); + assertThat(e.getMessage(), containsString("Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/")); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java index 785de25822c..e1ebe208e36 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java @@ -2,17 +2,12 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.entity.TermConceptMap; -import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; -import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; -import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; +import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; -import org.hl7.fhir.r4.model.UriType; import org.junit.*; import org.junit.rules.ExpectedException; import org.slf4j.Logger; @@ -21,6 +16,7 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import java.io.IOException; import java.util.List; import java.util.Optional; @@ -31,6 +27,8 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Rule public final ExpectedException expectedException = ExpectedException.none(); private IIdType myConceptMapId; + private IIdType myExtensionalCsId; + private IIdType myExtensionalVsId; @Before public void before() { @@ -40,6 +38,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @After public void after() { myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); + myDaoConfig.setPreExpandValueSetsExperimental(new DaoConfig().isPreExpandValueSetsExperimental()); } private void createAndPersistConceptMap() { @@ -56,6 +55,39 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { }); } + private void loadAndPersistCodeSystemAndValueSet() throws IOException { + loadAndPersistCodeSystem(); + loadAndPersistValueSet(); + } + + private void loadAndPersistCodeSystem() throws IOException { + CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); + persistCodeSystem(codeSystem); + } + + private void persistCodeSystem(CodeSystem theCodeSystem) { + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); + } + }); + } + + private void loadAndPersistValueSet() throws IOException { + ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + persistValueSet(valueSet); + } + + private void persistValueSet(ValueSet theValueSet) { + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + myExtensionalVsId = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); + } + }); + } + @Test public void testCreateConceptMapWithMissingSourceSystem() { ConceptMap conceptMap = new ConceptMap(); @@ -137,6 +169,16 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { } + @Test + public void testDuplicateCodeSystemUrls() throws Exception { + loadAndPersistCodeSystem(); + + expectedException.expect(UnprocessableEntityException.class); + expectedException.expectMessage("Can not create multiple CodeSystem resources with CodeSystem.url \"http://acme.org\", already have one with resource ID: CodeSystem/" + myExtensionalCsId.getIdPart()); + + loadAndPersistCodeSystem(); + } + @Test public void testDuplicateConceptMapUrls() { createAndPersistConceptMap(); @@ -147,6 +189,19 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { createAndPersistConceptMap(); } + @Test + public void testDuplicateValueSetUrls() throws Exception { + myDaoConfig.setPreExpandValueSetsExperimental(true); + + // DM 2019-03-05 - We pre-load our custom CodeSystem otherwise pre-expansion of the ValueSet will fail. + loadAndPersistCodeSystemAndValueSet(); + + expectedException.expect(UnprocessableEntityException.class); + expectedException.expectMessage("Can not create multiple ValueSet resources with ValueSet.url \"http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2\", already have one with resource ID: ValueSet/" + myExtensionalVsId.getIdPart()); + + loadAndPersistValueSet(); + } + @Test public void testStoreTermConceptMapAndChildren() { createAndPersistConceptMap(); @@ -325,6 +380,63 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { }); } + @Test + public void testStoreTermValueSetAndChildren() throws Exception { + myDaoConfig.setPreExpandValueSetsExperimental(true); + + loadAndPersistCodeSystemAndValueSet(); + + CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem)); + + ValueSet valueSet = myValueSetDao.read(myExtensionalVsId); + ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet)); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong()); + assertTrue(optionalValueSetByResourcePid.isPresent()); + + Optional optionalValueSetByUrl = myTermValueSetDao.findByUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"); + assertTrue(optionalValueSetByUrl.isPresent()); + + TermValueSet valueSet = optionalValueSetByUrl.get(); + assertSame(optionalValueSetByResourcePid.get(), valueSet); + ourLog.info("ValueSet:\n" + valueSet.toString()); + assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", valueSet.getUrl()); + assertEquals("Terminology Services Connectation #1 Extensional case #2", valueSet.getName()); + assertEquals(codeSystem.getConcept().size(), valueSet.getCodes().size()); + + TermValueSetCode code = valueSet.getCodes().get(0); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("8450-9", code.getCode()); + assertEquals("Systolic blood pressure--expiration", code.getDisplay()); + + code = valueSet.getCodes().get(1); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("11378-7", code.getCode()); + assertEquals("Systolic blood pressure at First encounter", code.getDisplay()); + + // ... + + code = valueSet.getCodes().get(22); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("8491-3", code.getCode()); + assertEquals("Systolic blood pressure 1 hour minimum", code.getDisplay()); + + code = valueSet.getCodes().get(23); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("8492-1", code.getCode()); + assertEquals("Systolic blood pressure 8 hour minimum", code.getDisplay()); + } + }); + } + @Test public void testTranslateByCodeSystemsAndSourceCodeOneToMany() { createAndPersistConceptMap(); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 852e9204c22..163c11a0626 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -75,6 +75,41 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { .renameColumn("mySystem", "SYSTEM_URL") .renameColumn("mySystemVersion", "SYSTEM_VERSION") .renameColumn("myValueSet", "VALUESET_URL"); + + // TermValueSet + version.startSectionWithMessage("Processing table: TRM_VALUESET"); + version.addIdGenerator("SEQ_VALUESET_PID"); + Builder.BuilderAddTableByColumns termValueSetTable = version.addTableByColumns("TRM_VALUESET", "PID"); + termValueSetTable.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetTable.addColumn("URL").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + termValueSetTable + .addIndex("IDX_VALUESET_URL") + .unique(true) + .withColumns("URL"); + termValueSetTable.addColumn("RES_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetTable + .addForeignKey("FK_TRMVALUESET_RES") + .toColumn("RES_ID") + .references("HFJ_RESOURCE", "RES_ID"); + termValueSetTable.addColumn("NAME").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + + // TermValueSetCode + version.startSectionWithMessage("Processing table: TRM_VALUESET_CODE"); + version.addIdGenerator("SEQ_VALUESET_CODE_PID"); + Builder.BuilderAddTableByColumns termValueSetCodeTable = version.addTableByColumns("TRM_VALUESET_CODE", "PID"); + termValueSetCodeTable.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetCodeTable.addColumn("VALUESET_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetCodeTable + .addForeignKey("FK_TRM_VALUESET_PID") + .toColumn("VALUESET_PID") + .references("TRM_VALUESET", "PID"); + termValueSetCodeTable.addColumn("SYSTEM").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + termValueSetCodeTable.addColumn("CODE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + termValueSetCodeTable + .addIndex("IDX_VALUESET_CODE_CS_CD") + .unique(false) + .withColumns("SYSTEM", "CODE"); + termValueSetCodeTable.addColumn("DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); }