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/ITermValueSetConceptDao.java similarity index 75% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptDao.java index 9c2be4bfb47..e613c17022b 100644 --- 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/ITermValueSetConceptDao.java @@ -20,16 +20,16 @@ package ca.uhn.fhir.jpa.dao.data; * #L% */ -import ca.uhn.fhir.jpa.entity.TermValueSetCode; +import ca.uhn.fhir.jpa.entity.TermValueSetConcept; 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 { +public interface ITermValueSetConceptDao extends JpaRepository { - @Query("DELETE FROM TermValueSetCode vsc WHERE vsc.myValueSet.myId = :pid") + @Query("DELETE FROM TermValueSetConcept vsc WHERE vsc.myValueSet.myId = :pid") @Modifying - void deleteTermValueSetCodesByValueSetId(@Param("pid") Long theValueSetId); + void deleteTermValueSetConceptsByValueSetId(@Param("pid") Long theValueSetId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptDesignationDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptDesignationDao.java new file mode 100644 index 00000000000..6861c86f59a --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptDesignationDao.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.TermValueSetConceptDesignation; +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 ITermValueSetConceptDesignationDao extends JpaRepository { + + @Query("DELETE FROM TermValueSetConceptDesignation vscd WHERE vscd.myConcept.myValueSet.myId = :pid") + @Modifying + void deleteTermValueSetConceptDesignationsByValueSetId(@Param("pid") Long theValueSetId); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index fdc24dac791..b183525ff1a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptDesignation; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -51,8 +52,7 @@ import java.util.Date; import java.util.List; import java.util.Set; -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 implements IFhirResourceDaoCodeSystem { @@ -193,6 +193,21 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 implements IFhirResourceDaoCodeSystem { @@ -168,6 +167,21 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4 i termConcept.setDisplay(next.getDisplay()); termConcept.addChildren(toPersistedConcepts(next.getConcept(), theCodeSystemVersion), RelationshipTypeEnum.ISA); retVal.add(termConcept); + + for (CodeSystem.ConceptDefinitionDesignationComponent designationComponent : next.getDesignation()) { + if (isNotBlank(designationComponent.getValue())) { + TermConceptDesignation designation = termConcept.addDesignation(); + designation.setLanguage(designationComponent.hasLanguage() ? designationComponent.getLanguage() : null); + if (designationComponent.hasUse()) { + designation.setUseSystem(designationComponent.getUse().hasSystem() ? designationComponent.getUse().getSystem() : null); + designation.setUseCode(designationComponent.getUse().hasCode() ? designationComponent.getUse().getCode() : null); + designation.setUseDisplay(designationComponent.getUse().hasDisplay() ? designationComponent.getUse().getDisplay() : null); + } + designation.setValue(designationComponent.getValue()); + } + } + + // TODO: DM 2019-07-16 - We should also populate TermConceptProperty entities here. } } 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 4aeea98ccae..6fab5a701e7 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 @@ -103,7 +103,6 @@ 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; } 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 index 88638f3271e..f586a9df8db 100644 --- 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 @@ -66,7 +66,7 @@ public class TermValueSet implements Serializable { private String myName; @OneToMany(mappedBy = "myValueSet") - private List myCodes; + private List myConcepts; public Long getId() { return myId; @@ -102,12 +102,12 @@ public class TermValueSet implements Serializable { return this; } - public List getCodes() { - if (myCodes == null) { - myCodes = new ArrayList<>(); + public List getConcepts() { + if (myConcepts == null) { + myConcepts = new ArrayList<>(); } - return myCodes; + return myConcepts; } @Override @@ -138,7 +138,7 @@ public class TermValueSet implements Serializable { .append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)")) .append("myResourcePid", myResourcePid) .append("myName", myName) - .append(myCodes != null ? ("myCodes - size=" + myCodes.size()) : ("myCodes=(null)")) + .append(myConcepts != null ? ("myConcepts - size=" + myConcepts.size()) : ("myConcepts=(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/TermValueSetConcept.java similarity index 78% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetConcept.java index 88fc16f5b1f..49de4907693 100644 --- 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/TermValueSetConcept.java @@ -29,20 +29,22 @@ 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_CODE", indexes = { - @Index(name = "IDX_VALUESET_CODE_CS_CD", columnList = "SYSTEM, CODE") +@Table(name = "TRM_VALUESET_CONCEPT", indexes = { + @Index(name = "IDX_VALUESET_CONCEPT_CS_CD", columnList = "SYSTEM, CODE") }) @Entity() -public class TermValueSetCode implements Serializable { +public class TermValueSetConcept 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") + @SequenceGenerator(name = "SEQ_VALUESET_CONCEPT_PID", sequenceName = "SEQ_VALUESET_CONCEPT_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_CONCEPT_PID") @Column(name = "PID") private Long myId; @@ -65,6 +67,9 @@ public class TermValueSetCode implements Serializable { @Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH) private String myDisplay; + @OneToMany(mappedBy = "myConcept") + private List myDesignations; + public Long getId() { return myId; } @@ -73,7 +78,7 @@ public class TermValueSetCode implements Serializable { return myValueSet; } - public TermValueSetCode setValueSet(TermValueSet theValueSet) { + public TermValueSetConcept setValueSet(TermValueSet theValueSet) { myValueSet = theValueSet; return this; } @@ -98,7 +103,7 @@ public class TermValueSetCode implements Serializable { return mySystem; } - public TermValueSetCode setSystem(@Nonnull String theSystem) { + public TermValueSetConcept 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)); @@ -110,7 +115,7 @@ public class TermValueSetCode implements Serializable { return myCode; } - public TermValueSetCode setCode(@Nonnull String theCode) { + public TermValueSetConcept 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)); @@ -122,18 +127,26 @@ public class TermValueSetCode implements Serializable { return myDisplay; } - public TermValueSetCode setDisplay(String theDisplay) { + public TermValueSetConcept setDisplay(String theDisplay) { myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH); return this; } + public List getDesignations() { + if (myDesignations == null) { + myDesignations = new ArrayList<>(); + } + + return myDesignations; + } + @Override public boolean equals(Object theO) { if (this == theO) return true; - if (!(theO instanceof TermValueSetCode)) return false; + if (!(theO instanceof TermValueSetConcept)) return false; - TermValueSetCode that = (TermValueSetCode) theO; + TermValueSetConcept that = (TermValueSetConcept) theO; return new EqualsBuilder() .append(getValueSetUrl(), that.getValueSetUrl()) @@ -161,6 +174,7 @@ public class TermValueSetCode implements Serializable { .append("mySystem", mySystem) .append("myCode", myCode) .append("myDisplay", myDisplay) + .append(myDesignations != null ? ("myDesignations - size=" + myDesignations.size()) : ("myDesignations=(null)")) .toString(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetConceptDesignation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetConceptDesignation.java new file mode 100644 index 00000000000..b1ab2cff713 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetConceptDesignation.java @@ -0,0 +1,177 @@ +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_C_DESIGNATION", indexes = { + @Index(name = "IDX_VALUESET_C_DSGNTN_VAL", columnList = "VAL") +}) +@Entity() +public class TermValueSetConceptDesignation implements Serializable { + private static final long serialVersionUID = 1L; + + public static final int MAX_LENGTH = 500; + + @Id() + @SequenceGenerator(name = "SEQ_VALUESET_C_DSGNTN_PID", sequenceName = "SEQ_VALUESET_C_DSGNTN_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_C_DSGNTN_PID") + @Column(name = "PID") + private Long myId; + + @ManyToOne() + @JoinColumn(name = "VALUESET_CONCEPT_PID", referencedColumnName = "PID", nullable = false, foreignKey = @ForeignKey(name = "FK_TRM_VALUESET_CONCEPT_PID")) + private TermValueSetConcept myConcept; + + @Column(name = "LANG", nullable = true, length = MAX_LENGTH) + private String myLanguage; + + @Column(name = "USE_SYSTEM", nullable = true, length = MAX_LENGTH) + private String myUseSystem; + + @Column(name = "USE_CODE", nullable = true, length = MAX_LENGTH) + private String myUseCode; + + @Column(name = "USE_DISPLAY", nullable = true, length = MAX_LENGTH) + private String myUseDisplay; + + @Column(name = "VAL", nullable = false, length = MAX_LENGTH) + private String myValue; + + public Long getId() { + return myId; + } + + public TermValueSetConcept getConcept() { + return myConcept; + } + + public TermValueSetConceptDesignation setConcept(TermValueSetConcept theConcept) { + myConcept = theConcept; + return this; + } + + public String getLanguage() { + return myLanguage; + } + + public TermValueSetConceptDesignation setLanguage(String theLanguage) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theLanguage, MAX_LENGTH, + "Language exceeds maximum length (" + MAX_LENGTH + "): " + length(theLanguage)); + myLanguage = theLanguage; + return this; + } + + public String getUseSystem() { + return myUseSystem; + } + + public TermValueSetConceptDesignation setUseSystem(String theUseSystem) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseSystem, MAX_LENGTH, + "Use system exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseSystem)); + myUseSystem = theUseSystem; + return this; + } + + public String getUseCode() { + return myUseCode; + } + + public TermValueSetConceptDesignation setUseCode(String theUseCode) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseCode, MAX_LENGTH, + "Use code exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseCode)); + myUseCode = theUseCode; + return this; + } + + public String getUseDisplay() { + return myUseDisplay; + } + + public TermValueSetConceptDesignation setUseDisplay(String theUseDisplay) { + myUseDisplay = left(theUseDisplay, MAX_LENGTH); + return this; + } + + public String getValue() { + return myValue; + } + + public TermValueSetConceptDesignation 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; + } + + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (!(theO instanceof TermValueSetConceptDesignation)) return false; + + TermValueSetConceptDesignation that = (TermValueSetConceptDesignation) theO; + + return new EqualsBuilder() + .append(getLanguage(), that.getLanguage()) + .append(getUseSystem(), that.getUseSystem()) + .append(getUseCode(), that.getUseCode()) + .append(getUseDisplay(), that.getUseDisplay()) + .append(getValue(), that.getValue()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getLanguage()) + .append(getUseSystem()) + .append(getUseCode()) + .append(getUseDisplay()) + .append(getValue()) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("myId", myId) + .append(myConcept != null ? ("myConcept - id=" + myConcept.getId()) : ("myConcept=(null)")) + .append("myLanguage", myLanguage) + .append("myUseSystem", myUseSystem) + .append("myUseCode", myUseCode) + .append("myUseDisplay", myUseDisplay) + .append("myValue", myValue) + .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 165f1918d60..3119b7350af 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 @@ -91,8 +91,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, ApplicationContextAware { public static final int DEFAULT_FETCH_SIZE = 250; @@ -121,7 +120,9 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, @Autowired protected ITermValueSetDao myValueSetDao; @Autowired - protected ITermValueSetCodeDao myValueSetCodeDao; + protected ITermValueSetConceptDao myValueSetConceptDao; + @Autowired + protected ITermValueSetConceptDesignationDao myValueSetConceptDesignationDao; @Autowired protected FhirContext myContext; @PersistenceContext(type = PersistenceContextType.TRANSACTION) @@ -152,54 +153,39 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, @Autowired(required = false) private IFulltextSearchSvc myFulltextSearchSvc; - - private void addCodeIfNotAlreadyAdded(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) { + private void addCodeIfNotAlreadyAdded(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) { String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri(); String code = theConcept.getCode(); String display = theConcept.getDisplay(); Collection designations = theConcept.getDesignations(); - addCodeIfNotAlreadyAdded(theExpansionComponent, theAddedCodes, designations, theAdd, theCodeCounter, codeSystem, code, display); + addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, designations, theAdd, theCodeCounter, codeSystem, code, display); } - private void addCodeIfNotAlreadyAdded(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, Collection theDesignations, boolean theAdd, AtomicInteger theCodeCounter, String theCodeSystem, String theCode, String theDisplay) { - if (isNotBlank(theCode) && theAdd && theAddedCodes.add(theCode)) { - ValueSet.ValueSetExpansionContainsComponent contains = theExpansionComponent.addContains(); - contains.setCode(theCode); - contains.setSystem(theCodeSystem); - contains.setDisplay(theDisplay); - if (theDesignations != null) { - for (TermConceptDesignation nextDesignation : theDesignations) { - contains - .addDesignation() - .setValue(nextDesignation.getValue()) - .getUse() - .setSystem(nextDesignation.getUseSystem()) - .setCode(nextDesignation.getUseCode()) - .setDisplay(nextDesignation.getUseDisplay()); + private void addCodeIfNotAlreadyAdded(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set theAddedCodes, Collection theDesignations, boolean theAdd, AtomicInteger theCodeCounter, String theCodeSystem, String theCode, String theDisplay) { + if (isNoneBlank(theCodeSystem, theCode)) { + if (theAdd && theAddedCodes.add(theCodeSystem + "|" + theCode)) { + theValueSetCodeAccumulator.includeCodeWithDesignations(theCodeSystem, theCode, theDisplay, theDesignations); + theCodeCounter.incrementAndGet(); + } + + if (!theAdd && theAddedCodes.remove(theCodeSystem + "|" + theCode)) { + theValueSetCodeAccumulator.excludeCode(theCodeSystem, theCode); + theCodeCounter.decrementAndGet(); + } + } + } + + private void addConceptsToList(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set theAddedCodes, String theSystem, List theConcept, boolean theAdd) { + for (CodeSystem.ConceptDefinitionComponent next : theConcept) { + if (isNoneBlank(theSystem, next.getCode())) { + if (theAdd && theAddedCodes.add(theSystem + "|" + next.getCode())) { + theValueSetCodeAccumulator.includeCode(theSystem, next.getCode(), next.getDisplay()); + } + if (!theAdd && theAddedCodes.remove(theSystem + "|" + next.getCode())) { + theValueSetCodeAccumulator.excludeCode(theSystem, next.getCode()); } } - - theCodeCounter.incrementAndGet(); - } - - if (!theAdd && theAddedCodes.remove(theCode)) { - removeCodeFromExpansion(theCodeSystem, theCode, theExpansionComponent); - theCodeCounter.decrementAndGet(); - } - } - - private void addConceptsToList(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, String theSystem, List theConcept, boolean theAdd) { - for (CodeSystem.ConceptDefinitionComponent next : theConcept) { - if (theAdd && theAddedCodes.add(next.getCode())) { - ValueSet.ValueSetExpansionContainsComponent contains = theExpansionComponent.addContains(); - contains.setCode(next.getCode()); - contains.setSystem(theSystem); - contains.setDisplay(next.getDisplay()); - } - if (!theAdd && theAddedCodes.remove(next.getCode())) { - removeCodeFromExpansion(theSystem, next.getCode(), theExpansionComponent); - } - addConceptsToList(theExpansionComponent, theAddedCodes, theSystem, next.getConcept(), theAdd); + addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, theSystem, next.getConcept(), theAdd); } } @@ -400,12 +386,14 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, TermValueSet existingTermValueSet = optionalExistingTermValueSetById.get(); ourLog.info("Deleting existing TermValueSet {} and its children...", existingTermValueSet.getId()); - myValueSetCodeDao.deleteTermValueSetCodesByValueSetId(existingTermValueSet.getId()); + myValueSetConceptDesignationDao.deleteTermValueSetConceptDesignationsByValueSetId(existingTermValueSet.getId()); + myValueSetConceptDao.deleteTermValueSetConceptsByValueSetId(existingTermValueSet.getId()); myValueSetDao.deleteTermValueSetById(existingTermValueSet.getId()); ourLog.info("Done deleting existing TermValueSet {} and its children.", existingTermValueSet.getId()); ourLog.info("Flushing..."); - myValueSetCodeDao.flush(); + myValueSetConceptDesignationDao.flush(); + myValueSetConceptDao.flush(); myValueSetDao.flush(); ourLog.info("Done flushing."); } @@ -466,26 +454,13 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, @Transactional(propagation = Propagation.REQUIRED) public ValueSet expandValueSet(ValueSet theValueSetToExpand) { - ValueSet.ValueSetExpansionComponent expansionComponent = new ValueSet.ValueSetExpansionComponent(); + ValueSetExpansionComponentWithCodeAccumulator expansionComponent = new ValueSetExpansionComponentWithCodeAccumulator(); expansionComponent.setIdentifier(UUID.randomUUID().toString()); expansionComponent.setTimestamp(new Date()); - Set addedCodes = new HashSet<>(); AtomicInteger codeCounter = new AtomicInteger(0); - // Handle includes - ourLog.debug("Handling includes"); - for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getInclude()) { - boolean add = true; - expandValueSetHandleIncludeOrExclude(expansionComponent, addedCodes, include, add, codeCounter); - } - - // Handle excludes - ourLog.debug("Handling excludes"); - for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getExclude()) { - boolean add = false; - expandValueSetHandleIncludeOrExclude(expansionComponent, addedCodes, include, add, codeCounter); - } + expandValueSet(theValueSetToExpand, expansionComponent, codeCounter); expansionComponent.setTotal(codeCounter.get()); @@ -496,6 +471,30 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return valueSet; } + @Override + @Transactional(propagation = Propagation.REQUIRED) + public void expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) { + expandValueSet(theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0)); + } + + private void expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator, AtomicInteger theCodeCounter) { + Set addedCodes = new HashSet<>(); + + // Handle includes + ourLog.debug("Handling includes"); + for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getInclude()) { + boolean add = true; + expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter); + } + + // Handle excludes + ourLog.debug("Handling excludes"); + for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getExclude()) { + boolean add = false; + expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter); + } + } + protected List expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.r4.model.ValueSet theValueSetToExpandR4) { org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSet(theValueSetToExpandR4).getExpansion(); @@ -507,7 +506,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return retVal; } - public void expandValueSetHandleIncludeOrExclude(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, ValueSet.ConceptSetComponent theInclude, boolean theAdd, AtomicInteger theCodeCounter) { + private void expandValueSetHandleIncludeOrExclude(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theInclude, boolean theAdd, AtomicInteger theCodeCounter) { String system = theInclude.getSystem(); boolean hasSystem = isNotBlank(system); boolean hasValueSet = theInclude.getValueSet().size() > 0; @@ -525,7 +524,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, * since we're going to do it without the database. */ if (myFulltextSearchSvc == null) { - expandWithoutHibernateSearch(theExpansionComponent, theAddedCodes, theInclude, system, theAdd, theCodeCounter); + expandWithoutHibernateSearch(theValueSetCodeAccumulator, theAddedCodes, theInclude, system, theAdd, theCodeCounter); return; } @@ -649,7 +648,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, for (Object next : jpaQuery.getResultList()) { count.incrementAndGet(); TermConcept concept = (TermConcept) next; - addCodeIfNotAlreadyAdded(theExpansionComponent, theAddedCodes, concept, theAdd, theCodeCounter); + addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, concept, theAdd, theCodeCounter); } @@ -670,24 +669,21 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, if (theInclude.getConcept().isEmpty() == false) { for (ValueSet.ConceptReferenceComponent next : theInclude.getConcept()) { String nextCode = next.getCode(); - if (isNotBlank(nextCode) && !theAddedCodes.contains(nextCode)) { + if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) { CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode); if (code != null) { - if (theAdd && theAddedCodes.add(nextCode)) { - ValueSet.ValueSetExpansionContainsComponent contains = theExpansionComponent.addContains(); - contains.setCode(nextCode); - contains.setSystem(system); - contains.setDisplay(code.getDisplay()); + if (theAdd && theAddedCodes.add(system + "|" + nextCode)) { + theValueSetCodeAccumulator.includeCode(system, nextCode, code.getDisplay()); } - if (!theAdd && theAddedCodes.remove(nextCode)) { - removeCodeFromExpansion(system, nextCode, theExpansionComponent); + if (!theAdd && theAddedCodes.remove(system + "|" + nextCode)) { + theValueSetCodeAccumulator.excludeCode(system, nextCode); } } } } } else { List concept = codeSystemFromContext.getConcept(); - addConceptsToList(theExpansionComponent, theAddedCodes, system, concept, theAdd); + addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, system, concept, theAdd); } } @@ -702,12 +698,12 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, myConceptDao .findByCodeSystemAndCode(codeSystem.getCurrentVersion(), nextConcept.getCode()) .ifPresent(concept -> - addCodeIfNotAlreadyAdded(theExpansionComponent, theAddedCodes, concept, theAdd, theCodeCounter) + addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, concept, theAdd, theCodeCounter) ); } - if (!theAdd && theAddedCodes.remove(nextConcept.getCode())) { - removeCodeFromExpansion(nextConcept.getSystem(), nextConcept.getCode(), theExpansionComponent); + if (isNoneBlank(nextConcept.getSystem(), nextConcept.getCode()) && !theAdd && theAddedCodes.remove(nextConcept.getSystem() + "|" + nextConcept.getCode())) { + theValueSetCodeAccumulator.excludeCode(nextConcept.getSystem(), nextConcept.getCode()); } } @@ -717,9 +713,11 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, } } - private void expandWithoutHibernateSearch(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd, AtomicInteger theCodeCounter) { + private void expandWithoutHibernateSearch(IValueSetCodeAccumulator theValueSetCodeAccumulator, 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 expand ValueSet with parameters - Hibernate Search is not enabled on this server."); + if (theValueSetCodeAccumulator instanceof ValueSetExpansionComponentWithCodeAccumulator) { + Validate.isTrue(((ValueSetExpansionComponentWithCodeAccumulator) theValueSetCodeAccumulator).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."); @@ -727,7 +725,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, if (!theSystem.equals(theInclude.getSystem())) { continue; } - addCodeIfNotAlreadyAdded(theExpansionComponent, theAddedCodes, null, theAdd, theCodeCounter, theSystem, next.getCode(), next.getDisplay()); + addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, null, theAdd, theCodeCounter, theSystem, next.getCode(), next.getDisplay()); } } @@ -1038,14 +1036,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, } - private void removeCodeFromExpansion(String theCodeSystem, String theCode, ValueSet.ValueSetExpansionComponent theExpansionComponent) { - theExpansionComponent - .getContains() - .removeIf(t -> - theCodeSystem.equals(t.getSystem()) && - theCode.equals(t.getCode())); - } - private int saveConcept(TermConcept theConcept) { int retVal = 0; @@ -1144,7 +1134,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, List existing = myCodeSystemVersionDao.findByCodeSystemResource(theCodeSystemResourcePid); /* - * For now we always delete old versions.. At some point it would be nice to allow configuration to keep old versions + * For now we always delete old versions. At some point it would be nice to allow configuration to keep old versions. */ ourLog.info("Deleting old code system versions"); @@ -1318,8 +1308,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, TermConceptMapGroupElement termConceptMapGroupElement; for (ConceptMap.SourceElementComponent element : group.getElement()) { if (isBlank(element.getCode())) { - // FIXME: JA - send this to an interceptor message so it can be - // output + // FIXME: JA - send this to an interceptor message so it can be output continue; } termConceptMapGroupElement = new TermConceptMapGroupElement(); @@ -1386,26 +1375,53 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, Optional optionalExistingTermValueSetByUrl = myValueSetDao.findByUrl(url); if (!optionalExistingTermValueSetByUrl.isPresent()) { myValueSetDao.save(termValueSet); - int codesSaved = 0; + int conceptsSaved = 0; + int designationsSaved = 0; + // FIXME: DM 2019-07-15 - Here we should call expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator). + // FIXME: DM 2019-07-15 - We need an implementation IValueSetCodeAccumulator that saves ValueSetConcept records and their children. ValueSet expandedValueSet = expandValueSet(theValueSet); if (expandedValueSet.hasExpansion()) { if (expandedValueSet.getExpansion().hasTotal() && expandedValueSet.getExpansion().getTotal() > 0) { - TermValueSetCode code; + TermValueSetConcept concept; 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"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getSystem(), "ValueSet contains a concept with no system value"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getCode(), "ValueSet contains a concept 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); + concept = new TermValueSetConcept(); + concept.setValueSet(termValueSet); + concept.setSystem(contains.getSystem()); + concept.setCode(contains.getCode()); + concept.setDisplay(contains.hasDisplay() ? contains.getDisplay() : null); + myValueSetConceptDao.save(concept); - if (codesSaved++ % 250 == 0) { - ourLog.info("Have pre-expanded {} codes in ValueSet", codesSaved); - myValueSetCodeDao.flush(); + TermValueSetConceptDesignation designation; + for (ValueSet.ConceptReferenceDesignationComponent containedDesignation : contains.getDesignation()) { + ValidateUtil.isNotBlankOrThrowInvalidRequest(containedDesignation.getValue(), "ValueSet contains a concept designation with no value"); + + designation = new TermValueSetConceptDesignation(); + designation.setConcept(concept); + designation.setLanguage(containedDesignation.hasLanguage() ? containedDesignation.getLanguage() : null); + if (containedDesignation.hasUse()) { + designation.setUseSystem(containedDesignation.getUse().hasSystem() ? containedDesignation.getUse().getSystem() : null); + designation.setUseCode(containedDesignation.getUse().hasCode() ? containedDesignation.getUse().getCode() : null); + designation.setUseDisplay(containedDesignation.getUse().hasDisplay() ? containedDesignation.getUse().getDisplay() : null); + } + designation.setValue(containedDesignation.getValue()); + myValueSetConceptDesignationDao.save(designation); + + if (designationsSaved++ % 250 == 0) { + ourLog.info("Have pre-expanded {} designations in ValueSet", designationsSaved); + myValueSetConceptDesignationDao.flush(); + } + } + + // TODO: DM 2019-07-16 - We need TermValueSetConceptProperty, similar to TermConceptProperty. + // TODO: DM 2019-07-16 - We should also populate TermValueSetConceptProperty entities here. + + if (conceptsSaved++ % 250 == 0) { + ourLog.info("Have pre-expanded {} concepts in ValueSet", conceptsSaved); + myValueSetConceptDao.flush(); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java index 7bfec207ae1..5d750cb00a9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java @@ -86,6 +86,11 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl { throw new UnsupportedOperationException(); } + @Override + public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) { + throw new UnsupportedOperationException(); + } + @Override public List expandValueSet(String theValueSet) { throw new UnsupportedOperationException(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java index 99701c94060..ecd38e79851 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java @@ -25,8 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; /* * #%L @@ -171,6 +170,19 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen } } + @Override + public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) { + ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand; + + try { + org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; + valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand); + super.expandValueSet(valueSetToExpandR4, theValueSetCodeAccumulator); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + } + @Override public List expandValueSet(String theValueSet) { ValueSet vs = myValidationSupport.fetchResource(myContext, ValueSet.class, theValueSet); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java index ed11049c2a4..a8b849424a0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java @@ -131,6 +131,11 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements return super.expandValueSet(valueSetToExpand); } + @Override + public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) { + ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand; + super.expandValueSet(valueSetToExpand, theValueSetCodeAccumulator); + } @Override public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { 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 4d50f75afb5..ffd0540ef4b 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 @@ -41,11 +41,15 @@ public interface IHapiTerminologySvc { ValueSet expandValueSet(ValueSet theValueSetToExpand); + void expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator); + /** * Version independent */ IBaseResource expandValueSet(IBaseResource theValueSetToExpand); + void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator); + List expandValueSet(String theValueSet); Optional findCode(String theCodeSystem, String theCode); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetCodeAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetCodeAccumulator.java new file mode 100644 index 00000000000..a97608cb094 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetCodeAccumulator.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.term; + +/* + * #%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.TermConceptDesignation; + +import java.util.Collection; + +public interface IValueSetCodeAccumulator { + + void includeCode(String theSystem, String theCode, String theDisplay); + + void includeCodeWithDesignations(String theSystem, String theCode, String theDisplay, Collection theDesignations); + + void excludeCode(String theSystem, String theCode); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithCodeAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithCodeAccumulator.java new file mode 100644 index 00000000000..1fb98a9ff07 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithCodeAccumulator.java @@ -0,0 +1,65 @@ +package ca.uhn.fhir.jpa.term; + +/* + * #%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.TermConceptDesignation; +import org.hl7.fhir.r4.model.ValueSet; + +import java.util.Collection; + +public class ValueSetExpansionComponentWithCodeAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetCodeAccumulator { + @Override + public void includeCode(String theSystem, String theCode, String theDisplay) { + ValueSet.ValueSetExpansionContainsComponent contains = this.addContains(); + contains.setSystem(theSystem); + contains.setCode(theCode); + contains.setDisplay(theDisplay); + } + + @Override + public void includeCodeWithDesignations(String theSystem, String theCode, String theDisplay, Collection theDesignations) { + ValueSet.ValueSetExpansionContainsComponent contains = this.addContains(); + contains.setSystem(theSystem); + contains.setCode(theCode); + contains.setDisplay(theDisplay); + if (theDesignations != null) { + for (TermConceptDesignation termConceptDesignation : theDesignations) { + contains + .addDesignation() + .setValue(termConceptDesignation.getValue()) + .setLanguage(termConceptDesignation.getLanguage()) + .getUse() + .setSystem(termConceptDesignation.getUseSystem()) + .setCode(termConceptDesignation.getUseCode()) + .setDisplay(termConceptDesignation.getUseDisplay()); + } + } + } + + @Override + public void excludeCode(String theSystem, String theCode) { + this + .getContains() + .removeIf(t -> + theSystem.equals(t.getSystem()) && + theCode.equals(t.getCode())); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index 6311e1c4cd5..edd63ceb41a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -16,7 +16,6 @@ import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; @@ -27,7 +26,6 @@ import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.io.IOUtils; @@ -59,7 +57,6 @@ import java.io.InputStream; import java.util.Map; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {TestDstu3Config.class}) @@ -240,6 +237,10 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("myTaskDaoDstu3") protected IFhirResourceDao myTaskDao; @Autowired + protected ITermConceptDao myTermConceptDao; + @Autowired + protected ITermCodeSystemDao myTermCodeSystemDao; + @Autowired protected IHapiTerminologySvc myTermSvc; @Autowired protected PlatformTransactionManager myTransactionMgr; 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 c53347bfb69..40ede474561 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 @@ -130,6 +130,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Autowired protected ITermConceptDao myTermConceptDao; @Autowired + protected ITermConceptDesignationDao myTermConceptDesignationDao; + @Autowired @Qualifier("myConditionDaoR4") protected IFhirResourceDao myConditionDao; @Autowired @@ -293,7 +295,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Autowired protected ITermValueSetDao myTermValueSetDao; @Autowired - protected ITermValueSetCodeDao myTermValueSetCodeDao; + protected ITermValueSetConceptDao myTermValueSetConceptDao; + @Autowired + protected ITermValueSetConceptDesignationDao myTermValueSetConceptDesignationDao; @Autowired protected ITermConceptMapDao myTermConceptMapDao; @Autowired 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 3089835220d..13d3e4eb8b8 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 @@ -1,12 +1,12 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; 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.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; @@ -19,7 +19,11 @@ import org.hl7.fhir.r4.model.ValueSet; import org.junit.After; import org.junit.AfterClass; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; import java.util.ArrayList; import java.util.List; @@ -30,11 +34,13 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { + private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplDstu3Test.class); private static final String CS_URL = "http://example.com/my_code_system"; private static final String CS_URL_2 = "http://example.com/my_code_system2"; - @Autowired - private ITermCodeSystemDao myTermCodeSystemDao; + + private IIdType myExtensionalCsId; + private IIdType myExtensionalVsId; @After public void after() { @@ -76,6 +82,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { childAAB.addPropertyString("propA", "valueAAB"); childAAB.addPropertyString("propB", "foo"); childAAB.addDesignation() + .setLanguage("D1L") .setUseSystem("D1S") .setUseCode("D1C") .setUseDisplay("D1D") @@ -546,13 +553,13 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { assertEquals("childAAB", concept.getCode()); assertEquals("http://example.com/my_code_system", concept.getSystem()); assertEquals(null, concept.getDisplay()); + assertEquals("D1L", concept.getDesignation().get(0).getLanguage()); assertEquals("D1S", concept.getDesignation().get(0).getUse().getSystem()); assertEquals("D1C", concept.getDesignation().get(0).getUse().getCode()); assertEquals("D1D", concept.getDesignation().get(0).getUse().getDisplay()); assertEquals("D1V", concept.getDesignation().get(0).getValue()); } - @Test public void testStoreCodeSystemInvalidCyclicLoop() { CodeSystem codeSystem = new CodeSystem(); @@ -585,6 +592,103 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { } } + @Test + public void testStoreTermCodeSystemAndNestedChildren() { + IIdType codeSystemId = createCodeSystem(); + CodeSystem codeSystemResource = myCodeSystemDao.read(codeSystemId); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystemResource)); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + TermCodeSystem codeSystem = myTermCodeSystemDao.findByResourcePid(codeSystemId.getIdPartAsLong()); + assertEquals(CS_URL, codeSystem.getCodeSystemUri()); + assertEquals("SYSTEM NAME", codeSystem.getName()); + + TermCodeSystemVersion codeSystemVersion = codeSystem.getCurrentVersion(); + assertEquals(9, codeSystemVersion.getConcepts().size()); + + List concepts = myTermConceptDao.findByCodeSystemVersion(codeSystemVersion); + + TermConcept parentWithNoChildrenA = concepts.get(0); + assertEquals("ParentWithNoChildrenA", parentWithNoChildrenA.getCode()); + assertNull(parentWithNoChildrenA.getDisplay()); + assertEquals(0, parentWithNoChildrenA.getChildren().size()); + assertEquals(0, parentWithNoChildrenA.getParents().size()); + assertEquals(0, parentWithNoChildrenA.getDesignations().size()); + assertEquals(0, parentWithNoChildrenA.getProperties().size()); + + TermConcept parentWithNoChildrenB = concepts.get(1); + assertEquals("ParentWithNoChildrenB", parentWithNoChildrenB.getCode()); + assertNull(parentWithNoChildrenB.getDisplay()); + assertEquals(0, parentWithNoChildrenB.getChildren().size()); + assertEquals(0, parentWithNoChildrenB.getParents().size()); + assertEquals(0, parentWithNoChildrenB.getDesignations().size()); + assertEquals(0, parentWithNoChildrenB.getProperties().size()); + + TermConcept parentWithNoChildrenC = concepts.get(2); + assertEquals("ParentWithNoChildrenC", parentWithNoChildrenC.getCode()); + assertNull(parentWithNoChildrenC.getDisplay()); + assertEquals(0, parentWithNoChildrenC.getChildren().size()); + assertEquals(0, parentWithNoChildrenC.getParents().size()); + assertEquals(0, parentWithNoChildrenC.getDesignations().size()); + assertEquals(0, parentWithNoChildrenC.getProperties().size()); + + TermConcept parentA = concepts.get(3); + assertEquals("ParentA", parentA.getCode()); + assertNull(parentA.getDisplay()); + assertEquals(2, parentA.getChildren().size()); + assertEquals(0, parentA.getParents().size()); + assertEquals(0, parentA.getDesignations().size()); + assertEquals(0, parentA.getProperties().size()); + + TermConcept childAA = concepts.get(4); + assertEquals("childAA", childAA.getCode()); + assertNull(childAA.getDisplay()); + assertEquals(2, childAA.getChildren().size()); + assertEquals(1, childAA.getParents().size()); + assertSame(parentA, childAA.getParents().iterator().next().getParent()); + assertEquals(0, childAA.getDesignations().size()); + assertEquals(0, childAA.getProperties().size()); + + TermConcept childAAA = concepts.get(5); + assertEquals("childAAA", childAAA.getCode()); + assertNull(childAAA.getDisplay()); + assertEquals(0, childAAA.getChildren().size()); + assertEquals(1, childAAA.getParents().size()); + assertSame(childAA, childAAA.getParents().iterator().next().getParent()); + assertEquals(0, childAAA.getDesignations().size()); + assertEquals(2, childAAA.getProperties().size()); + + TermConcept childAAB = concepts.get(6); + assertEquals("childAAB", childAAB.getCode()); + assertNull(childAAB.getDisplay()); + assertEquals(0, childAAB.getChildren().size()); + assertEquals(1, childAAB.getParents().size()); + assertSame(childAA, childAAB.getParents().iterator().next().getParent()); + assertEquals(1, childAAB.getDesignations().size()); + assertEquals(2, childAAB.getProperties().size()); + + TermConcept childAB = concepts.get(7); + assertEquals("childAB", childAB.getCode()); + assertNull(childAB.getDisplay()); + assertEquals(0, childAB.getChildren().size()); + assertEquals(1, childAB.getParents().size()); + assertSame(parentA, childAB.getParents().iterator().next().getParent()); + assertEquals(0, childAB.getDesignations().size()); + assertEquals(0, childAB.getProperties().size()); + + TermConcept parentB = concepts.get(8); + assertEquals("ParentB", parentB.getCode()); + assertNull(parentB.getDisplay()); + assertEquals(0, parentB.getChildren().size()); + assertEquals(0, parentB.getParents().size()); + assertEquals(0, parentB.getDesignations().size()); + assertEquals(0, parentB.getProperties().size()); + } + }); + } + /** * Check that a custom ValueSet against a custom CodeSystem expands correctly */ 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 e1ebe208e36..31e0c7c1936 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 @@ -3,6 +3,7 @@ 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.*; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; @@ -10,6 +11,7 @@ import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.junit.*; import org.junit.rules.ExpectedException; +import org.mockito.Mock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.TransactionStatus; @@ -21,6 +23,9 @@ import java.util.List; import java.util.Optional; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class TerminologySvcImplR4Test extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplR4Test.class); @@ -30,6 +35,9 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { private IIdType myExtensionalCsId; private IIdType myExtensionalVsId; + @Mock + IValueSetCodeAccumulator myValueSetCodeAccumulator; + @Before public void before() { myDaoConfig.setAllowExternalReferences(true); @@ -40,6 +48,57 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setPreExpandValueSetsExperimental(new DaoConfig().isPreExpandValueSetsExperimental()); } + + private IIdType createCodeSystem() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(CS_URL); + codeSystem.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalArgumentException::new); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + + TermConcept parent; + parent = new TermConcept(cs, "ParentWithNoChildrenA"); + cs.getConcepts().add(parent); + parent = new TermConcept(cs, "ParentWithNoChildrenB"); + cs.getConcepts().add(parent); + parent = new TermConcept(cs, "ParentWithNoChildrenC"); + cs.getConcepts().add(parent); + + TermConcept parentA = new TermConcept(cs, "ParentA"); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA"); + parentA.addChild(childAA, TermConceptParentChildLink.RelationshipTypeEnum.ISA); + + TermConcept childAAA = new TermConcept(cs, "childAAA"); + childAAA.addPropertyString("propA", "valueAAA"); + childAAA.addPropertyString("propB", "foo"); + childAA.addChild(childAAA, TermConceptParentChildLink.RelationshipTypeEnum.ISA); + + TermConcept childAAB = new TermConcept(cs, "childAAB"); + childAAB.addPropertyString("propA", "valueAAB"); + childAAB.addPropertyString("propB", "foo"); + childAAB.addDesignation() + .setUseSystem("D1S") + .setUseCode("D1C") + .setUseDisplay("D1D") + .setValue("D1V"); + childAA.addChild(childAAB, TermConceptParentChildLink.RelationshipTypeEnum.ISA); + + TermConcept childAB = new TermConcept(cs, "childAB"); + parentA.addChild(childAB, TermConceptParentChildLink.RelationshipTypeEnum.ISA); + + TermConcept parentB = new TermConcept(cs, "ParentB"); + cs.getConcepts().add(parentB); + + myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs); + + return id; + } private void createAndPersistConceptMap() { ConceptMap conceptMap = createConceptMap(); @@ -60,11 +119,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { loadAndPersistValueSet(); } + private void loadAndPersistCodeSystemAndValueSetWithDesignations() throws IOException { + loadAndPersistCodeSystemWithDesignations(); + loadAndPersistValueSet(); + } + private void loadAndPersistCodeSystem() throws IOException { CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); persistCodeSystem(codeSystem); } + private void loadAndPersistCodeSystemWithDesignations() throws IOException { + CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-with-designations.xml"); + persistCodeSystem(codeSystem); + } + private void persistCodeSystem(CodeSystem theCodeSystem) { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override @@ -202,6 +271,173 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { loadAndPersistValueSet(); } + @Test + public void testExpandValueSetWithValueSetCodeAccumulator() { + createCodeSystem(); + + ValueSet vs = new ValueSet(); + ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(CS_URL); + + myTermSvc.expandValueSet(vs, myValueSetCodeAccumulator); + verify(myValueSetCodeAccumulator, times(9)).includeCodeWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection()); + } + + @Test + public void testStoreTermCodeSystemAndChildren() throws Exception { + loadAndPersistCodeSystemWithDesignations(); + + CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem)); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + TermCodeSystem codeSystem = myTermCodeSystemDao.findByResourcePid(myExtensionalCsId.getIdPartAsLong()); + assertEquals("http://acme.org", codeSystem.getCodeSystemUri()); + assertNull(codeSystem.getName()); + + TermCodeSystemVersion codeSystemVersion = codeSystem.getCurrentVersion(); + assertEquals(24, codeSystemVersion.getConcepts().size()); + + List concepts = myTermConceptDao.findByCodeSystemVersion(codeSystemVersion); + + TermConcept concept = concepts.get(0); + assertEquals("8450-9", concept.getCode()); + assertEquals("Systolic blood pressure--expiration", concept.getDisplay()); + assertEquals(1, concept.getDesignations().size()); + + TermConceptDesignation designation = concept.getDesignations().iterator().next(); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk - expiratie", designation.getValue()); + + concept = concepts.get(1); + assertEquals("11378-7", concept.getCode()); + assertEquals("Systolic blood pressure at First encounter", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); + + // ... + + concept = concepts.get(22); + assertEquals("8491-3", concept.getCode()); + assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay()); + assertEquals(1, concept.getDesignations().size()); + + designation = concept.getDesignations().iterator().next(); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue()); + + concept = concepts.get(23); + assertEquals("8492-1", concept.getCode()); + assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); + } + }); + } + + @Test + public void testStoreTermCodeSystemAndNestedChildren() { + IIdType codeSystemId = createCodeSystem(); + CodeSystem codeSystemResource = myCodeSystemDao.read(codeSystemId); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystemResource)); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + TermCodeSystem codeSystem = myTermCodeSystemDao.findByResourcePid(codeSystemId.getIdPartAsLong()); + assertEquals(CS_URL, codeSystem.getCodeSystemUri()); + assertEquals("SYSTEM NAME", codeSystem.getName()); + + TermCodeSystemVersion codeSystemVersion = codeSystem.getCurrentVersion(); + assertEquals(9, codeSystemVersion.getConcepts().size()); + + List concepts = myTermConceptDao.findByCodeSystemVersion(codeSystemVersion); + + TermConcept parentWithNoChildrenA = concepts.get(0); + assertEquals("ParentWithNoChildrenA", parentWithNoChildrenA.getCode()); + assertNull(parentWithNoChildrenA.getDisplay()); + assertEquals(0, parentWithNoChildrenA.getChildren().size()); + assertEquals(0, parentWithNoChildrenA.getParents().size()); + assertEquals(0, parentWithNoChildrenA.getDesignations().size()); + assertEquals(0, parentWithNoChildrenA.getProperties().size()); + + TermConcept parentWithNoChildrenB = concepts.get(1); + assertEquals("ParentWithNoChildrenB", parentWithNoChildrenB.getCode()); + assertNull(parentWithNoChildrenB.getDisplay()); + assertEquals(0, parentWithNoChildrenB.getChildren().size()); + assertEquals(0, parentWithNoChildrenB.getParents().size()); + assertEquals(0, parentWithNoChildrenB.getDesignations().size()); + assertEquals(0, parentWithNoChildrenB.getProperties().size()); + + TermConcept parentWithNoChildrenC = concepts.get(2); + assertEquals("ParentWithNoChildrenC", parentWithNoChildrenC.getCode()); + assertNull(parentWithNoChildrenC.getDisplay()); + assertEquals(0, parentWithNoChildrenC.getChildren().size()); + assertEquals(0, parentWithNoChildrenC.getParents().size()); + assertEquals(0, parentWithNoChildrenC.getDesignations().size()); + assertEquals(0, parentWithNoChildrenC.getProperties().size()); + + TermConcept parentA = concepts.get(3); + assertEquals("ParentA", parentA.getCode()); + assertNull(parentA.getDisplay()); + assertEquals(2, parentA.getChildren().size()); + assertEquals(0, parentA.getParents().size()); + assertEquals(0, parentA.getDesignations().size()); + assertEquals(0, parentA.getProperties().size()); + + TermConcept childAA = concepts.get(4); + assertEquals("childAA", childAA.getCode()); + assertNull(childAA.getDisplay()); + assertEquals(2, childAA.getChildren().size()); + assertEquals(1, childAA.getParents().size()); + assertSame(parentA, childAA.getParents().iterator().next().getParent()); + assertEquals(0, childAA.getDesignations().size()); + assertEquals(0, childAA.getProperties().size()); + + TermConcept childAAA = concepts.get(5); + assertEquals("childAAA", childAAA.getCode()); + assertNull(childAAA.getDisplay()); + assertEquals(0, childAAA.getChildren().size()); + assertEquals(1, childAAA.getParents().size()); + assertSame(childAA, childAAA.getParents().iterator().next().getParent()); + assertEquals(0, childAAA.getDesignations().size()); + assertEquals(2, childAAA.getProperties().size()); + + TermConcept childAAB = concepts.get(6); + assertEquals("childAAB", childAAB.getCode()); + assertNull(childAAB.getDisplay()); + assertEquals(0, childAAB.getChildren().size()); + assertEquals(1, childAAB.getParents().size()); + assertSame(childAA, childAAB.getParents().iterator().next().getParent()); + assertEquals(1, childAAB.getDesignations().size()); + assertEquals(2, childAAB.getProperties().size()); + + TermConcept childAB = concepts.get(7); + assertEquals("childAB", childAB.getCode()); + assertNull(childAB.getDisplay()); + assertEquals(0, childAB.getChildren().size()); + assertEquals(1, childAB.getParents().size()); + assertSame(parentA, childAB.getParents().iterator().next().getParent()); + assertEquals(0, childAB.getDesignations().size()); + assertEquals(0, childAB.getProperties().size()); + + TermConcept parentB = concepts.get(8); + assertEquals("ParentB", parentB.getCode()); + assertNull(parentB.getDisplay()); + assertEquals(0, parentB.getChildren().size()); + assertEquals(0, parentB.getParents().size()); + assertEquals(0, parentB.getDesignations().size()); + assertEquals(0, parentB.getProperties().size()); + } + }); + } + @Test public void testStoreTermConceptMapAndChildren() { createAndPersistConceptMap(); @@ -384,7 +620,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { public void testStoreTermValueSetAndChildren() throws Exception { myDaoConfig.setPreExpandValueSetsExperimental(true); - loadAndPersistCodeSystemAndValueSet(); + loadAndPersistCodeSystemAndValueSetWithDesignations(); CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId); ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem)); @@ -406,33 +642,51 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { 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()); + assertEquals(codeSystem.getConcept().size(), valueSet.getConcepts().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()); + TermValueSetConcept concept = valueSet.getConcepts().get(0); + ourLog.info("Code:\n" + concept.toString()); + assertEquals("http://acme.org", concept.getSystem()); + assertEquals("8450-9", concept.getCode()); + assertEquals("Systolic blood pressure--expiration", concept.getDisplay()); + assertEquals(1, concept.getDesignations().size()); - 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()); + TermValueSetConceptDesignation designation = concept.getDesignations().get(0); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk - expiratie", designation.getValue()); + + concept = valueSet.getConcepts().get(1); + ourLog.info("Code:\n" + concept.toString()); + assertEquals("http://acme.org", concept.getSystem()); + assertEquals("11378-7", concept.getCode()); + assertEquals("Systolic blood pressure at First encounter", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); // ... - 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()); + concept = valueSet.getConcepts().get(22); + ourLog.info("Code:\n" + concept.toString()); + assertEquals("http://acme.org", concept.getSystem()); + assertEquals("8491-3", concept.getCode()); + assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay()); + assertEquals(1, concept.getDesignations().size()); - 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()); + designation = concept.getDesignations().get(0); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue()); + + concept = valueSet.getConcepts().get(23); + ourLog.info("Code:\n" + concept.toString()); + assertEquals("http://acme.org", concept.getSystem()); + assertEquals("8492-1", concept.getCode()); + assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); } }); } diff --git a/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-with-designations.xml b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-with-designations.xml new file mode 100644 index 00000000000..e3bad86cb07 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-with-designations.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 15134724cd5..dd366cb8463 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 @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.migrate.tasks; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermValueSet; +import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.taskdef.AddColumnTask; import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask; @@ -96,23 +97,43 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { .references("HFJ_RESOURCE", "RES_ID"); termValueSetTable.addColumn("NAME").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSet.MAX_NAME_LENGTH); - // 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 + // TermValueSetConcept + version.startSectionWithMessage("Processing table: TRM_VALUESET_CONCEPT"); + version.addIdGenerator("SEQ_VALUESET_CONCEPT_PID"); + Builder.BuilderAddTableByColumns termValueSetConceptTable = version.addTableByColumns("TRM_VALUESET_CONCEPT", "PID"); + termValueSetConceptTable.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetConceptTable.addColumn("VALUESET_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetConceptTable .addForeignKey("FK_TRM_VALUESET_PID") .toColumn("VALUESET_PID") .references("TRM_VALUESET", "PID"); - termValueSetCodeTable.addColumn("SYSTEM").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermCodeSystem.MAX_URL_LENGTH); - termValueSetCodeTable.addColumn("CODE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_CODE_LENGTH); - termValueSetCodeTable - .addIndex("IDX_VALUESET_CODE_CS_CD") + termValueSetConceptTable.addColumn("SYSTEM").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermCodeSystem.MAX_URL_LENGTH); + termValueSetConceptTable.addColumn("CODE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_CODE_LENGTH); + termValueSetConceptTable + .addIndex("IDX_VALUESET_CONCEPT_CS_CD") .unique(false) .withColumns("SYSTEM", "CODE"); - termValueSetCodeTable.addColumn("DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_DESC_LENGTH); + termValueSetConceptTable.addColumn("DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_DESC_LENGTH); + + // TermValueSetConceptDesignation + version.startSectionWithMessage("Processing table: TRM_VALUESET_C_DESIGNATION"); + version.addIdGenerator("SEQ_VALUESET_C_DSGNTN_PID"); + Builder.BuilderAddTableByColumns termValueSetConceptDesignationTable = version.addTableByColumns("TRM_VALUESET_C_DESIGNATION", "PID"); + termValueSetConceptDesignationTable.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetConceptDesignationTable.addColumn("VALUESET_CONCEPT_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetConceptDesignationTable + .addForeignKey("FK_TRM_VALUESET_CONCEPT_PID") + .toColumn("VALUESET_CONCEPT_PID") + .references("TRM_VALUESET_CONCEPT", "PID"); + termValueSetConceptDesignationTable.addColumn("LANG").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSetConceptDesignation.MAX_LENGTH); + termValueSetConceptDesignationTable.addColumn("USE_SYSTEM").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSetConceptDesignation.MAX_LENGTH); + termValueSetConceptDesignationTable.addColumn("USE_CODE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSetConceptDesignation.MAX_LENGTH); + termValueSetConceptDesignationTable.addColumn("USE_DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSetConceptDesignation.MAX_LENGTH); + termValueSetConceptDesignationTable.addColumn("VAL").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSetConceptDesignation.MAX_LENGTH); + termValueSetConceptDesignationTable + .addIndex("IDX_VALUESET_C_DSGNTN_VAL") + .unique(false) + .withColumns("VAL"); } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 7c9f875844c..0cee8fd6ae0 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -284,6 +284,13 @@ profiles via the $snapshot operation, and will automatically generate a snapshot when needed for validation. + + Creating/updating CodeSystems now persist CodeSystem.concept.designation]]> to + the terminology tables. + + + Expanded ValueSets now populate ValueSet.expansion.contains.designation.language]]>. +