diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index c5c51aab27f..5107dcf324f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -101,6 +101,7 @@ public class BaseDstu3Config extends BaseConfig { @Bean(name = "mySystemProviderDstu3") public ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3 systemProviderDstu3() { ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3 retVal = new ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3(); + retVal.setContext(defaultFhirContext()); retVal.setDao(systemDaoDstu3()); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index dc2fc9dcf7b..aa5a3d302dd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1174,7 +1174,7 @@ public abstract class BaseHapiFhirDao implements IDao { coordsParams = extractSearchParamCoords(theEntity, theResource); // ourLog.info("Indexing resource: {}", entity.getId()); - ourLog.info("Storing date indexes: {}", dateParams); + ourLog.trace("Storing date indexes: {}", dateParams); tokenParams = new HashSet(); for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index 9391b6443a2..a853e5320b1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -37,8 +37,6 @@ import javax.persistence.criteria.Root; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Propagation; @@ -50,7 +48,6 @@ import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; import ca.uhn.fhir.jpa.entity.ForcedId; import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.util.ReindexFailureException; import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.model.api.TagList; @@ -89,7 +86,6 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao extends BaseHapiFhirDao() { - @Override - public Integer doInTransaction(TransactionStatus theStatus) { - - int maxResult = 10000; - Page resources = myTermConceptDao.findResourcesRequiringReindexing(new PageRequest(0, maxResult)); - if (resources.hasContent() == false) { - return 0; - } - - ourLog.info("Indexing {} / {} concepts", resources.getContent().size(), resources.getTotalElements()); - - int count = 0; - long start = System.currentTimeMillis(); - - for (TermConcept resourceTable : resources) { - resourceTable.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); - myTermConceptDao.save(resourceTable); - count++; - } - - long delay = System.currentTimeMillis() - start; - long avg = (delay / resources.getContent().size()); - ourLog.info("Indexed {} / {} concepts in {}ms - Avg {}ms / resource", new Object[] { count, resources.getContent().size(), delay, avg }); - - return resources.getContent().size(); - } - }); - } - @Override public TagList getAllTags(RequestDetails theRequestDetails) { // Notify interceptors diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java index 5d991db55a0..e6c5eb0ac5a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java @@ -78,7 +78,7 @@ public class FhirResourceDaoValueSetDstu2 extends FhirResourceDaoDstu2 } @Override - public ValueSet expand(IIdType theId, String theFilter) { + public ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) { ValueSet source = loadValueSetForExpansion(theId); return expand(source, theFilter); @@ -179,7 +179,7 @@ public class FhirResourceDaoValueSetDstu2 extends FhirResourceDaoDstu2 } @Override - public ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, IPrimitiveType theSystem, IPrimitiveType theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept) { + public ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, IPrimitiveType theSystem, IPrimitiveType theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequestDetails) { List valueSetIds; boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0; @@ -212,7 +212,7 @@ public class FhirResourceDaoValueSetDstu2 extends FhirResourceDaoDstu2 } for (IIdType nextId : valueSetIds) { - ValueSet expansion = expand(nextId, null); + ValueSet expansion = expand(nextId, null, theRequestDetails); List contains = expansion.getExpansion().getContains(); ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept); if (result != null) { @@ -314,7 +314,7 @@ public class FhirResourceDaoValueSetDstu2 extends FhirResourceDaoDstu2 List valueSetIds = findCodeSystemIdsContainingSystemAndCode(code, system); for (IIdType nextId : valueSetIds) { - ValueSet expansion = expand(nextId, null); + ValueSet expansion = expand(nextId, null, theRequestDetails); List contains = expansion.getExpansion().getContains(); LookupCodeResult result = lookup(contains, system, code); if (result != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java index ade8b580700..3e1cd0e499e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java @@ -24,9 +24,11 @@ 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 ca.uhn.fhir.rest.method.RequestDetails; + public interface IFhirResourceDaoValueSet extends IFhirResourceDao { - T expand(IIdType theId, String theFilter); + T expand(IIdType theId, String theFilter, RequestDetails theRequestDetails); T expand(T theSource, String theFilter); @@ -34,7 +36,7 @@ public interface IFhirResourceDaoValueSet exten void purgeCaches(); - ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, IPrimitiveType theSystem, IPrimitiveType theDisplay, CD theCoding, CC theCodeableConcept); + ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, IPrimitiveType theSystem, IPrimitiveType theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails); public class ValidateCodeResult { private String myDisplay; 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 a8d4c040c6a..422c7e36044 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 @@ -26,6 +26,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.nio.file.FileVisitOption; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -56,6 +57,7 @@ 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.util.LogicUtil; +import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ElementUtil; @@ -70,8 +72,8 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 private IFhirResourceDaoCodeSystem myCodeSystemDao; @Override - public ValueSet expand(IIdType theId, String theFilter) { - ValueSet source = myValidationSupport.fetchResource(getContext(), ValueSet.class, theId.getValue()); + public ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) { + ValueSet source = read(theId, theRequestDetails); return expand(source, theFilter); } @@ -86,6 +88,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 ValueSetExpansionComponent expansion = outcome.getValueset().getExpansion(); ValueSet retVal = new ValueSet(); + retVal.getMeta().setLastUpdated(new Date()); retVal.setExpansion(expansion); return retVal; } @@ -166,7 +169,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 @Override public ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, - IPrimitiveType theSystem, IPrimitiveType theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) { + IPrimitiveType theSystem, IPrimitiveType theDisplay, Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { List valueSetIds = Collections.emptyList(); List codeSystemIds = Collections.emptyList(); @@ -205,7 +208,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 } for (IIdType nextId : valueSetIds) { - ValueSet expansion = expand(nextId, null); + ValueSet expansion = expand(nextId, null, theRequestDetails); List contains = expansion.getExpansion().getContains(); ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept); if (result != null) { @@ -219,7 +222,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 } for (IIdType nextId : codeSystemIds) { - ValueSet expansion = expand(nextId, null); + ValueSet expansion = expand(nextId, null, theRequestDetails); List contains = expansion.getExpansion().getContains(); ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept); if (result != null) { 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 db13d0ee3df..23c31396a28 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 @@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -39,6 +40,8 @@ import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import javax.persistence.Transient; @@ -49,47 +52,56 @@ 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.apache.lucene.analysis.core.WhitespaceTokenizerFactory; import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.Analyzer; +import org.hibernate.search.annotations.AnalyzerDef; +import org.hibernate.search.annotations.AnalyzerDefs; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Fields; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.Store; +import org.hibernate.search.annotations.TokenizerDef; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; //@formatter:off @Entity -@Indexed() +@Indexed(interceptor=DeferConceptIndexingInterceptor.class) @Table(name="TRM_CONCEPT", uniqueConstraints= { @UniqueConstraint(name="IDX_CONCEPT_CS_CODE", columnNames= {"CODESYSTEM_PID", "CODE"}) }, indexes= { @Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList="INDEX_STATUS") }) +@AnalyzerDefs({ + @AnalyzerDef(name = "conceptParentPidsAnalyzer", + tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class), + filters = { + }) +}) //@formatter:on public class TermConcept implements Serializable { private 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; - @OneToMany(fetch=FetchType.LAZY, mappedBy="myParent") + @OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade= {}) private Collection myChildren; - - @Column(name="CODE", length=100, nullable=false) - @Fields({ - @Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")), - }) + + @Column(name = "CODE", length = 100, nullable = false) + @Fields({ @Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")), }) private String myCode; @ManyToOne() - @JoinColumn(name="CODESYSTEM_PID", referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_CONCEPT_PID_CS_PID")) + @JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID")) private TermCodeSystemVersion myCodeSystem; - @Column(name="CODESYSTEM_PID", insertable=false, updatable=false) - @Fields({ - @Field(name="myCodeSystemVersionPid") - }) + @Column(name = "CODESYSTEM_PID", insertable = false, updatable = false) + @Fields({ @Field(name = "myCodeSystemVersionPid") }) private long myCodeSystemVersionPid; - + //@formatter:off @Column(name="DISPLAY", length=MAX_DESC_LENGTH, nullable=true) @Fields({ @@ -100,23 +112,21 @@ public class TermConcept implements Serializable { }) private String myDisplay; //@formatter:on - + @Id() - @SequenceGenerator(name="SEQ_CONCEPT_PID", sequenceName="SEQ_CONCEPT_PID") - @GeneratedValue(strategy=GenerationType.AUTO, generator="SEQ_CONCEPT_PID") - @Column(name="PID") + @SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PID") + @Column(name = "PID") private Long myId; @Column(name = "INDEX_STATUS", nullable = true) private Long myIndexStatus; @Transient - @Fields({ - @Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")), - }) + @Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "conceptParentPidsAnalyzer")) private String myParentPids; - @OneToMany(cascade= {}, fetch=FetchType.LAZY, mappedBy="myChild") + @OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myChild") private Collection myParents; public TermConcept() { @@ -135,7 +145,7 @@ public class TermConcept implements Serializable { link.setChild(theChild); link.setRelationshipType(theRelationshipType); getChildren().add(link); - + theChild.getParents().add(link); return this; } @@ -154,8 +164,8 @@ public class TermConcept implements Serializable { if (theObj == this) { return true; } - - TermConcept obj = (TermConcept)theObj; + + TermConcept obj = (TermConcept) theObj; EqualsBuilder b = new EqualsBuilder(); b.append(myCodeSystem, obj.myCodeSystem); @@ -190,13 +200,17 @@ public class TermConcept implements Serializable { return myIndexStatus; } + public String getParentPidsAsString() { + return myParentPids; + } + public Collection getParents() { if (myParents == null) { myParents = new ArrayList(); } return myParents; } - + @Override public int hashCode() { HashCodeBuilder b = new HashCodeBuilder(); @@ -204,7 +218,29 @@ public class TermConcept implements Serializable { b.append(myCode); return b.toHashCode(); } - + + private void parentPids(TermConcept theNextConcept, Set theParentPids) { + for (TermConceptParentChildLink nextParentLink : theNextConcept.getParents()) { + TermConcept parent = nextParentLink.getParent(); + Long parentConceptId = parent.getId(); + Validate.notNull(parentConceptId); + if (parent != null && theParentPids.add(parentConceptId)) { + parentPids(parent, theParentPids); + } + } + } + + @PreUpdate + @PrePersist + public void prePersist() { + Set parentPids = new HashSet(); + TermConcept entity = this; + parentPids(entity, parentPids); + entity.setParentPids(parentPids); + + ourLog.trace("Code {}/{} has parents {}", entity.getId(), entity.getCode(), entity.getParentPidsAsString()); + } + public void setCode(String theCode) { myCode = theCode; } @@ -236,7 +272,11 @@ public class TermConcept implements Serializable { } b.append(next); } - + + if (b.length() == 0) { + b.append("NONE"); + } + myParentPids = b.toString(); } @@ -244,5 +284,4 @@ public class TermConcept implements Serializable { public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("code", myCode).append("display", myDisplay).build(); } - } 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 36c15af5e9c..7bf7355d3c6 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 @@ -96,7 +96,55 @@ public class TermConceptParentChildLink implements Serializable { } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((myChild == null) ? 0 : myChild.hashCode()); + result = prime * result + ((myCodeSystem == null) ? 0 : myCodeSystem.hashCode()); + result = prime * result + ((myParent == null) ? 0 : myParent.hashCode()); + result = prime * result + ((myRelationshipType == null) ? 0 : myRelationshipType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TermConceptParentChildLink other = (TermConceptParentChildLink) obj; + if (myChild == null) { + if (other.myChild != null) + return false; + } else if (!myChild.equals(other.myChild)) + return false; + if (myCodeSystem == null) { + if (other.myCodeSystem != null) + return false; + } else if (!myCodeSystem.equals(other.myCodeSystem)) + return false; + if (myParent == null) { + if (other.myParent != null) + return false; + } else if (!myParent.equals(other.myParent)) + return false; + if (myRelationshipType != other.myRelationshipType) + return false; + return true; + } + + public enum RelationshipTypeEnum{ + // ******************************************** + // IF YOU ADD HERE MAKE SURE ORDER IS PRESERVED ISA } + + + public Long getId() { + return myPid; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java index 38a87c41aad..c34e07cc683 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java @@ -53,7 +53,8 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst @IdParam(optional=true) IdDt theId, @OperationParam(name="valueSet", min=0, max=1) ValueSet theValueSet, @OperationParam(name="identifier", min=0, max=1) UriDt theIdentifier, - @OperationParam(name = "filter", min=0, max=1) StringDt theFilter) { + @OperationParam(name = "filter", min=0, max=1) StringDt theFilter, + RequestDetails theRequestDetails) { //@formatter:on boolean haveId = theId != null && theId.hasIdPart(); @@ -72,7 +73,7 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst try { IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao(); if (haveId) { - return dao.expand(theId, toFilterString(theFilter)); + return dao.expand(theId, toFilterString(theFilter), theRequestDetails); } else if (haveIdentifier) { return dao.expandByIdentifier(theIdentifier.getValue(), toFilterString(theFilter)); } else { @@ -155,14 +156,15 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst @OperationParam(name="system", min=0, max=1) UriDt theSystem, @OperationParam(name="display", min=0, max=1) StringDt theDisplay, @OperationParam(name="coding", min=0, max=1) CodingDt theCoding, - @OperationParam(name="codeableConcept", min=0, max=1) CodeableConceptDt theCodeableConcept + @OperationParam(name="codeableConcept", min=0, max=1) CodeableConceptDt theCodeableConcept, + RequestDetails theRequestDetails ) { //@formatter:on startRequest(theServletRequest); try { IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao(); - ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept); + ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); Parameters retVal = new Parameters(); retVal.addParameter().setName("result").setValue(new BooleanDt(result.isResult())); if (isNotBlank(result.getMessage())) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java index a868f5f144f..42be82a19a0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java @@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDstu3 { @@ -50,7 +51,8 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst @IdParam(optional=true) IdType theId, @OperationParam(name="valueSet", min=0, max=1) ValueSet theValueSet, @OperationParam(name="identifier", min=0, max=1) UriType theIdentifier, - @OperationParam(name = "filter", min=0, max=1) StringType theFilter) { + @OperationParam(name = "filter", min=0, max=1) StringType theFilter, + RequestDetails theRequestDetails) { //@formatter:on boolean haveId = theId != null && theId.hasIdPart(); @@ -69,7 +71,7 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst try { IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao(); if (haveId) { - return dao.expand(theId, toFilterString(theFilter)); + return dao.expand(theId, toFilterString(theFilter), theRequestDetails); } else if (haveIdentifier) { return dao.expandByIdentifier(theIdentifier.getValue(), toFilterString(theFilter)); } else { @@ -118,14 +120,15 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst @OperationParam(name="system", min=0, max=1) UriType theSystem, @OperationParam(name="display", min=0, max=1) StringType theDisplay, @OperationParam(name="coding", min=0, max=1) Coding theCoding, - @OperationParam(name="codeableConcept", min=0, max=1) CodeableConcept theCodeableConcept + @OperationParam(name="codeableConcept", min=0, max=1) CodeableConcept theCodeableConcept, + RequestDetails theRequestDetails ) { //@formatter:on startRequest(theServletRequest); try { IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao(); - ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept); + ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); Parameters retVal = new Parameters(); retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult())); if (isNotBlank(result.getMessage())) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DeferConceptIndexingInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DeferConceptIndexingInterceptor.java new file mode 100644 index 00000000000..405016638bf --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DeferConceptIndexingInterceptor.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.search; + +import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor; +import org.hibernate.search.indexes.interceptor.IndexingOverride; + +import ca.uhn.fhir.jpa.entity.TermConcept; + +public class DeferConceptIndexingInterceptor implements EntityIndexingInterceptor { + + @Override + public IndexingOverride onAdd(TermConcept theEntity) { + if (theEntity.getIndexStatus() == null) { + return IndexingOverride.SKIP; + } + + return IndexingOverride.APPLY_DEFAULT; + } + + @Override + public IndexingOverride onCollectionUpdate(TermConcept theEntity) { + return IndexingOverride.APPLY_DEFAULT; + } + + + @Override + public IndexingOverride onDelete(TermConcept theEntity) { + return IndexingOverride.APPLY_DEFAULT; + } + + @Override + public IndexingOverride onUpdate(TermConcept theEntity) { + return onAdd(theEntity); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java index 6953964590e..bd89522dfdc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvc.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.term; */ import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; @@ -32,10 +33,17 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; +import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; import com.google.common.base.Stopwatch; @@ -50,6 +58,8 @@ 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; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ObjectUtil; @@ -85,6 +95,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { protected EntityManager myEntityManager; private boolean myProcessDeferred = true; + private long myNextReindexPass; private boolean addToSet(Set theSetToPopulate, TermConcept theConcept) { boolean retVal = theSetToPopulate.add(theConcept); @@ -209,15 +220,6 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { return cs; } - private void parentPids(TermConcept theNextConcept, Set theParentPids) { - for (TermConceptParentChildLink nextParentLink : theNextConcept.getParents()){ - TermConcept parent = nextParentLink.getParent(); - if (parent != null && theParentPids.add(parent.getId())) { - parentPids(parent, theParentPids); - } - } - } - private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap theConceptsStack, int theTotalConcepts) { if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) { return; @@ -231,12 +233,8 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { theConcept.setCodeSystem(theCodeSystem); theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); - Set parentPids = new HashSet(); - parentPids(theConcept, parentPids); - theConcept.setParentPids(parentPids); - if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { - myConceptDao.save(theConcept); + saveConcept(theConcept); } else { myConceptsToSaveLater.add(theConcept); } @@ -247,7 +245,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { for (TermConceptParentChildLink next : theConcept.getChildren()) { if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { - myConceptParentChildLinkDao.save(next); + saveConceptLink(next); } else { myConceptLinksToSaveLater.add(next); } @@ -255,6 +253,44 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { } + private void saveConceptLink(TermConceptParentChildLink next) { + if (next.getId() == null) { + myConceptParentChildLinkDao.save(next); + } + } + + private int saveConcept(TermConcept theConcept) { + int retVal = 0; + retVal += ensureParentsSaved(theConcept.getParents()); + if (theConcept.getId() == null || theConcept.getIndexStatus() == null) { + retVal++; + theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); + myConceptDao.saveAndFlush(theConcept); + } + + ourLog.trace("Saved {} and got PID {}", theConcept.getCode(), theConcept.getId()); + return retVal; + } + + private int ensureParentsSaved(Collection theParents) { + ourLog.trace("Checking {} parents", theParents.size()); + int retVal = 0; + + for (TermConceptParentChildLink nextLink : theParents) { + if (nextLink.getRelationshipType() == RelationshipTypeEnum.ISA) { + TermConcept nextParent = nextLink.getParent(); + retVal += ensureParentsSaved(nextParent.getParents()); + if (nextParent.getId() == null) { + myConceptDao.saveAndFlush(nextParent); + retVal++; + ourLog.debug("Saved parent code {} and got id {}", nextParent.getCode(), nextParent.getId()); + } + } + } + + return retVal; + } + private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) { if (theNext.getCodeSystem() != null) { return; @@ -270,30 +306,76 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { @Override public synchronized void saveDeferred() { if (!myProcessDeferred || ((myConceptsToSaveLater.isEmpty() && myConceptLinksToSaveLater.isEmpty()))) { - return; + processReindexing(); } int codeCount = 0, relCount = 0; + StopWatch stopwatch = new StopWatch(); int count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myConceptsToSaveLater.size()); ourLog.info("Saving {} deferred concepts...", count); while (codeCount < count && myConceptsToSaveLater.size() > 0) { TermConcept next = myConceptsToSaveLater.remove(0); - myConceptDao.save(next); - codeCount++; + codeCount += saveConcept(next); } + if (codeCount > 0) { + ourLog.info("Saved {} deferred concepts ({} codes remain and {} relationships remain) in {}ms ({}ms / code)", new Object[] {codeCount, myConceptsToSaveLater.size(), myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(codeCount)}); + } + if (codeCount == 0) { count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myConceptLinksToSaveLater.size()); ourLog.info("Saving {} deferred concept relationships...", count); while (relCount < count && myConceptLinksToSaveLater.size() > 0) { TermConceptParentChildLink next = myConceptLinksToSaveLater.remove(0); - myConceptParentChildLinkDao.save(next); + saveConceptLink(next); relCount++; } } - ourLog.info("Saved {} deferred concepts ({} remain) and {} deferred relationships ({} remain)", new Object[] {codeCount, myConceptsToSaveLater.size(), relCount, myConceptLinksToSaveLater.size()}); + if (relCount > 0) { + ourLog.info("Saved {} deferred relationships ({} remain) in {}ms ({}ms / code)", new Object[] {relCount, myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(codeCount)}); + } + + if ((myConceptsToSaveLater.size() + myConceptLinksToSaveLater.size()) == 0) { + ourLog.info("All deferred concepts and relationships have now been synchronized to the database"); + } + } + + @Autowired + private PlatformTransactionManager myTransactionMgr; + + private void processReindexing() { + if (System.currentTimeMillis() < myNextReindexPass) { + return; + } + + TransactionTemplate tt = new TransactionTemplate(myTransactionMgr); + tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + tt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + int maxResult = 1000; + Page resources = myConceptDao.findResourcesRequiringReindexing(new PageRequest(0, maxResult)); + if (resources.hasContent() == false) { + myNextReindexPass = System.currentTimeMillis() + DateUtils.MILLIS_PER_MINUTE; + return; + } + + ourLog.info("Indexing {} / {} concepts", resources.getContent().size(), resources.getTotalElements()); + + int count = 0; + StopWatch stopwatch = new StopWatch(); + + for (TermConcept resourceTable : resources) { + saveConcept(resourceTable); + count++; + } + + ourLog.info("Indexed {} / {} concepts in {}ms - Avg {}ms / resource", new Object[] { count, resources.getContent().size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(count) }); + } + }); + } @Override 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 8a78be41460..11c2fcc430c 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 @@ -29,6 +29,7 @@ import java.util.List; import java.util.Set; import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryWrapperFilter; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.FullTextQuery; import org.hibernate.search.query.dsl.BooleanJunction; @@ -78,56 +79,38 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements I @Autowired private ValueSetExpander myValueSetExpander; - @Override - public List expandValueSet(String theValueSet) { - ValueSet source = new ValueSet(); - source.getCompose().addImport(theValueSet); - try { - ArrayList retVal = new ArrayList(); - - ValueSetExpansionOutcome outcome = myValueSetExpander.expand(source); - for (ValueSetExpansionContainsComponent next : outcome.getValueset().getExpansion().getContains()) { - retVal.add(new VersionIndependentConcept(next.getSystem(), next.getCode())); - } - - return retVal; - - } catch (Exception e) { - throw new InternalErrorException(e); + private void addCodeIfNotAlreadyAdded(String system, ValueSetExpansionComponent retVal, Set addedCodes, TermConcept nextConcept) { + if (addedCodes.add(nextConcept.getCode())) { + ValueSetExpansionContainsComponent contains = retVal.addContains(); + contains.setCode(nextConcept.getCode()); + contains.setSystem(system); + contains.setDisplay(nextConcept.getDisplay()); } - } - @Override - @Transactional(propagation=Propagation.REQUIRED) - public void storeNewCodeSystemVersion(String theSystem, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails) { - CodeSystem cs = new org.hl7.fhir.dstu3.model.CodeSystem(); - cs.setUrl(theSystem); - cs.setContent(CodeSystemContentMode.NOTPRESENT); - - DaoMethodOutcome createOutcome = myCodeSystemResourceDao.create(cs, "CodeSystem?url=" + UrlUtil.escape(theSystem), theRequestDetails); - IIdType csId = createOutcome.getId().toUnqualifiedVersionless(); - if (createOutcome.getCreated() != Boolean.TRUE) { - CodeSystem existing = myCodeSystemResourceDao.read(csId, theRequestDetails); - csId = myCodeSystemResourceDao.update(existing, theRequestDetails).getId(); - - ourLog.info("Created new version of CodeSystem, got ID: {}", csId.toUnqualified().getValue()); - } - - ResourceTable resource = (ResourceTable) myCodeSystemResourceDao.readEntity(csId); - Long codeSystemResourcePid = resource.getId(); - - ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); - - theCodeSystemVersion.setResource(resource); - theCodeSystemVersion.setResourceVersionId(resource.getVersion()); - super.storeNewCodeSystemVersion(codeSystemResourcePid, theSystem, theCodeSystemVersion); + private void addDisplayFilterExact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { + bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery()); + } + private void addDisplayFilterInexact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { + //@formatter:off + Query textQuery = qb + .phrase() + .withSlop(2) + .onField("myDisplay").boostedTo(4.0f) + .andField("myDisplayEdgeNGram").boostedTo(2.0f) + //.andField("myDisplayNGram").boostedTo(1.0f) + //.andField("myDisplayPhonetic").boostedTo(0.5f) + .sentence(nextFilter.getValue().toLowerCase()).createQuery(); + bool.must(textQuery); + //@formatter:on } @Override public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { String system = theInclude.getSystem(); + ourLog.info("Starting expansion around code system: {}", system); + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system); TermCodeSystemVersion csv = cs.getCurrentVersion(); @@ -167,27 +150,40 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements I bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery()); for (ConceptSetFilterComponent nextFilter : theInclude.getFilter()) { - if (nextFilter.getProperty().equals("display") && nextFilter.getOp() == FilterOperator.EQUAL) { - if (isNotBlank(nextFilter.getValue())) { - bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery()); - } - } else if (nextFilter.getOp() == FilterOperator.ISA) { - if (isNotBlank(nextFilter.getValue())) { + if (isNotBlank(nextFilter.getValue())) { + if (nextFilter.getProperty().equals("display:exact") && nextFilter.getOp() == FilterOperator.EQUAL) { + addDisplayFilterExact(qb, bool, nextFilter); + } else if (nextFilter.getProperty().equals("display") && nextFilter.getOp() == FilterOperator.EQUAL) { + if (nextFilter.getValue().trim().contains(" ")) { + addDisplayFilterExact(qb, bool, nextFilter); + } else { + addDisplayFilterInexact(qb, bool, nextFilter); + } + } else if ((nextFilter.getProperty().equals("concept") || nextFilter.getProperty().equals("code")) && nextFilter.getOp() == FilterOperator.ISA) { TermConcept code = super.findCode(system, nextFilter.getValue()); + if (code == null) { + throw new InvalidRequestException("Invalid filter criteria - code does not exist: {" + system + "}" + nextFilter.getValue()); + } + + ourLog.info(" * Filtering on codes with a parent of {}/{}/{}", code.getId(), code.getCode(), code.getDisplay()); bool.must(qb.keyword().onField("myParentPids").matching("" + code.getId()).createQuery()); + } else { + throw new InvalidRequestException("Unknown filter property[" + nextFilter + "] + op[" + nextFilter.getOpElement().getValueAsString() + "]"); } - } else { - throw new InvalidRequestException("Unknown filter property[" + nextFilter + "] + op[" + nextFilter.getOpElement().getValueAsString() + "]"); } } Query luceneQuery = bool.createQuery(); FullTextQuery jpaQuery = em.createFullTextQuery(luceneQuery, TermConcept.class); + jpaQuery.setMaxResults(1000); + @SuppressWarnings("unchecked") List result = jpaQuery.getResultList(); for (TermConcept nextConcept : result) { addCodeIfNotAlreadyAdded(system, retVal, addedCodes, nextConcept); } + + retVal.setTotal(jpaQuery.getResultSize()); } if (!haveIncludeCriteria) { @@ -196,19 +192,29 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements I addCodeIfNotAlreadyAdded(system, retVal, addedCodes, nextConcept); } } - - retVal.setTotal(retVal.getContains().size()); - + + return retVal; } - private void addCodeIfNotAlreadyAdded(String system, ValueSetExpansionComponent retVal, Set addedCodes, TermConcept nextConcept) { - if (addedCodes.add(nextConcept.getCode())) { - ValueSetExpansionContainsComponent contains = retVal.addContains(); - contains.setCode(nextConcept.getCode()); - contains.setSystem(system); - contains.setDisplay(nextConcept.getDisplay()); + @Override + public List expandValueSet(String theValueSet) { + ValueSet source = new ValueSet(); + source.getCompose().addImport(theValueSet); + try { + ArrayList retVal = new ArrayList(); + + ValueSetExpansionOutcome outcome = myValueSetExpander.expand(source); + for (ValueSetExpansionContainsComponent next : outcome.getValueset().getExpansion().getContains()) { + retVal.add(new VersionIndependentConcept(next.getSystem(), next.getCode())); + } + + return retVal; + + } catch (Exception e) { + throw new InternalErrorException(e); } + } @Override @@ -238,6 +244,33 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements I return super.supportsSystem(theSystem); } + @Override + @Transactional(propagation = Propagation.REQUIRED) + public void storeNewCodeSystemVersion(String theSystem, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails) { + CodeSystem cs = new org.hl7.fhir.dstu3.model.CodeSystem(); + cs.setUrl(theSystem); + cs.setContent(CodeSystemContentMode.NOTPRESENT); + + DaoMethodOutcome createOutcome = myCodeSystemResourceDao.create(cs, "CodeSystem?url=" + UrlUtil.escape(theSystem), theRequestDetails); + IIdType csId = createOutcome.getId().toUnqualifiedVersionless(); + if (createOutcome.getCreated() != Boolean.TRUE) { + CodeSystem existing = myCodeSystemResourceDao.read(csId, theRequestDetails); + csId = myCodeSystemResourceDao.update(existing, theRequestDetails).getId(); + + ourLog.info("Created new version of CodeSystem, got ID: {}", csId.toUnqualified().getValue()); + } + + ResourceTable resource = (ResourceTable) myCodeSystemResourceDao.readEntity(csId); + Long codeSystemResourcePid = resource.getId(); + + ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); + + theCodeSystemVersion.setResource(resource); + theCodeSystemVersion.setResourceVersionId(resource.getVersion()); + super.storeNewCodeSystemVersion(codeSystemResourcePid, theSystem, theCodeSystemVersion); + + } + @CoverageIgnore @Override public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java index 7f4b6b18c03..db10be4c5fa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java @@ -24,15 +24,12 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -47,7 +44,6 @@ import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.csv.QuoteMode; -import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -102,6 +98,8 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { } ourLog.info(b.toString(), theConcept.getCode()); childIter.remove(); + nextChild.getParents().remove(next); + } else { dropCircularRefs(nextChild, theChain, theCode2concept, theCircularCounter); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java index d66e209a40b..4471fb4e4b8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java @@ -32,4 +32,14 @@ public class StopWatch { return retVal; } + public long getMillis() { + long now = System.currentTimeMillis(); + long retVal = now - myStarted; + return retVal; + } + + public double getMillisPerOperation(int theNumOperations) { + return ((double)getMillis()) / Math.max(1.0, theNumOperations); + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java index f61d58f1d43..ff577c54a84 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java @@ -16,7 +16,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; @@ -25,7 +24,6 @@ import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.UriDt; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { @@ -58,7 +56,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); } @@ -71,7 +69,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure--expiration", result.getDisplay()); } @@ -85,7 +83,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -99,7 +97,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = new StringDt("Systolic blood pressure at First encounterXXXX"); CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -113,7 +111,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = new StringDt("Systolic blood pressure at First encounter"); CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -127,7 +125,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7"); - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -141,7 +139,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -150,7 +148,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { public void testExpandById() throws IOException { String resp; - ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null); + ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); ourLog.info(resp); // @formatter:off @@ -175,7 +173,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { * Filter with display name */ - expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic")); + expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic"), mySrd); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); ourLog.info(resp); //@formatter:off @@ -188,7 +186,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { * Filter with code */ - expanded = myValueSetDao.expand(myExtensionalVsId, ("11378")); + expanded = myValueSetDao.expand(myExtensionalVsId, ("11378"), mySrd); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); ourLog.info(resp); //@formatter:off 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 559a38f4a42..0b6b82eb461 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,6 +1,7 @@ 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; @@ -37,142 +38,25 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { - private static final String URL_MY_VALUE_SET = "http://example.com/my_value_set"; - private static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3TerminologyTest.class); + public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system"; + public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set"; - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); + @After + public void after() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); } - @Test - public void testCodeSystemWithDefinedCodes() { - //@formatter:off - CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl(URL_MY_CODE_SYSTEM); - codeSystem.setContent(CodeSystemContentMode.COMPLETE); - codeSystem - .addConcept().setCode("A").setDisplay("Code A") - .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA")) - .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); - codeSystem - .addConcept().setCode("B").setDisplay("Code A") - .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code AA")) - .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code AB")); - //@formatter:on - - IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); - - Set codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "A"); - assertThat(toCodes(codes), containsInAnyOrder("A", "AA", "AB")); - + @Before + public void before() { + myDaoConfig.setMaximumExpansionSize(5000); } - @Test - @Ignore - public void testSearchCodeInEmptyValueSet() { - ValueSet valueSet = new ValueSet(); - valueSet.setUrl(URL_MY_VALUE_SET); - myValueSetDao.create(valueSet, mySrd); - - Observation obsAA = new Observation(); - obsAA.setStatus(ObservationStatus.FINAL); - obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA"); - myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless(); - - Observation obsBA = new Observation(); - obsBA.setStatus(ObservationStatus.FINAL); - obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); - myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); - - Observation obsCA = new Observation(); - obsCA.setStatus(ObservationStatus.FINAL); - obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); - - SearchParameterMap params; - - params = new SearchParameterMap(); - params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); - - params = new SearchParameterMap(); - params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); - params.add(Observation.SP_STATUS, new TokenParam(null, "final")); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); - } - - @Test - public void testSearchCodeInLocalCodesystem() { - createLocalCsAndVs(); - - Observation obsAA = new Observation(); - obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA"); - IIdType idAA = myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless(); - - Observation obsBA = new Observation(); - obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); - IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); - - Observation obsCA = new Observation(); - obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); - - SearchParameterMap params = new SearchParameterMap(); - params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAA.getValue(), idBA.getValue())); - - } - - @Test - public void testSearchCodeInExternalCodesystem() { - createExternalCsAndLocalVs(); - - Observation obsPA = new Observation(); - obsPA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("ParentA"); - IIdType idPA = myObservationDao.create(obsPA, mySrd).getId().toUnqualifiedVersionless(); - - Observation obsAAA = new Observation(); - obsAAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAA"); - IIdType idAAA = myObservationDao.create(obsAAA, mySrd).getId().toUnqualifiedVersionless(); - - Observation obsAAB = new Observation(); - obsAAB.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAB"); - IIdType idAAB = myObservationDao.create(obsAAB, mySrd).getId().toUnqualifiedVersionless(); - - Observation obsCA = new Observation(); - obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); - - SearchParameterMap params = new SearchParameterMap(); - params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAAA.getValue(), idAAB.getValue())); - - params = new SearchParameterMap(); - params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue())); - - params = new SearchParameterMap(); - params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue(), idAAA.getValue(), idAAB.getValue())); - - } - - - private void createExternalCsAndLocalVs() { - CodeSystem codeSystem = createExternalCs(); - - createLocalVs(codeSystem); - } - - private CodeSystem createExternalCs() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); @@ -203,179 +87,97 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); cs.getConcepts().add(parentB); + TermConcept childBA = new TermConcept(cs, "childBA").setDisplay("Child BA"); + childBA.addChild(childAAB, RelationshipTypeEnum.ISA); + parentB.addChild(childBA, RelationshipTypeEnum.ISA); + + TermConcept parentC = new TermConcept(cs, "ParentC").setDisplay("Parent C"); + cs.getConcepts().add(parentC); + + TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); + parentC.addChild(childCA, RelationshipTypeEnum.ISA); + myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, cs); return codeSystem; } + private void createExternalCsAndLocalVs() { + CodeSystem codeSystem = createExternalCs(); + + createLocalVs(codeSystem); + } + + private void createLocalCsAndVs() { + //@formatter:off + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") + .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) + ) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + codeSystem + .addConcept().setCode("B").setDisplay("Code B") + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); + //@formatter:on + myCodeSystemDao.create(codeSystem, mySrd); + + createLocalVs(codeSystem); + } + + + private void createLocalVs(CodeSystem codeSystem) { + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + valueSet.getCompose().addInclude().setSystem(codeSystem.getUrl()); + myValueSetDao.create(valueSet, mySrd); + } + + @Test - public void testSearchCodeBelowAndAboveUnknownCodeSystem() { + public void testCodeSystemCreateDuplicateFails() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); - SearchParameterMap params = new SearchParameterMap(); + codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + try { + 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()); + } + } + + @Test + public void testCodeSystemWithDefinedCodes() { + //@formatter:off + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA")) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + codeSystem + .addConcept().setCode("B").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code AA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code AB")); + //@formatter:on - params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); - - params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); - - params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); - assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + Set codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "A"); + assertThat(toCodes(codes), containsInAnyOrder("A", "AA", "AB")); } - @Test - public void testSearchCodeInFhirCodesystem() { - createLocalCsAndVs(); - - AuditEvent aeIn1 = new AuditEvent(); - aeIn1.getType().setSystem("http://nema.org/dicom/dicm").setCode("110102"); - IIdType idIn1 = myAuditEventDao.create(aeIn1, mySrd).getId().toUnqualifiedVersionless(); - - AuditEvent aeIn2 = new AuditEvent(); - aeIn2.getType().setSystem("http://hl7.org/fhir/audit-event-type").setCode("rest"); - IIdType idIn2 = myAuditEventDao.create(aeIn2, mySrd).getId().toUnqualifiedVersionless(); - - AuditEvent aeOut1 = new AuditEvent(); - aeOut1.getType().setSystem("http://example.com").setCode("foo"); - IIdType idOut1 = myAuditEventDao.create(aeOut1, mySrd).getId().toUnqualifiedVersionless(); - - SearchParameterMap params = new SearchParameterMap(); - params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); - assertThat(toUnqualifiedVersionlessIdValues(myAuditEventDao.search(params)), containsInAnyOrder(idIn1.getValue(), idIn2.getValue())); - - params = new SearchParameterMap(); - params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/v3-PurposeOfUse").setModifier(TokenParamModifier.IN)); - assertThat(toUnqualifiedVersionlessIdValues(myAuditEventDao.search(params)), empty()); - } - - /** - * Can't currently abort costly - */ - @Test - @Ignore - public void testRefuseCostlyExpansionFhirCodesystem() { - createLocalCsAndVs(); - myDaoConfig.setMaximumExpansionSize(1); - - SearchParameterMap params = new SearchParameterMap(); - params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); - try { - myAuditEventDao.search(params); - fail(); - } catch (InvalidRequestException e) { - assertEquals("", e.getMessage()); - } - } - - @Test - public void testRefuseCostlyExpansionLocalCodesystem() { - createLocalCsAndVs(); - myDaoConfig.setMaximumExpansionSize(1); - - SearchParameterMap params = new SearchParameterMap(); - params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.ABOVE)); - try { - myObservationDao.search(params); - fail(); - } catch (InvalidRequestException e) { - assertEquals("Expansion of ValueSet produced too many codes (maximum 1) - Operation aborted!", e.getMessage()); - } - } - - @Test - public void testExpandWithSystemAndCodesInLocalValueSet() { - createLocalCsAndVs(); - - ValueSet vs = new ValueSet(); - ConceptSetComponent include = vs.getCompose().addInclude(); - include.setSystem(URL_MY_CODE_SYSTEM); - include.addConcept().setCode("A"); - include.addConcept().setCode("AA"); - include.addConcept().setCode("AAA"); - include.addConcept().setCode("AB"); - - ValueSet result = myValueSetDao.expand(vs, null); - - String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); - ourLog.info(encoded); - - ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("A", "AA", "AAA", "AB")); - - int idx = codes.indexOf("AAA"); - assertEquals("AAA", result.getExpansion().getContains().get(idx).getCode()); - assertEquals("Code AAA", result.getExpansion().getContains().get(idx).getDisplay()); - assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem()); -// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical"); -// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical"); -// - } - - - - @Test - public void testExpandWithSystemAndCodesAndFilterInExternalValueSet() { - createExternalCsAndLocalVs(); - - ValueSet vs = new ValueSet(); - ConceptSetComponent include = vs.getCompose().addInclude(); - include.setSystem(URL_MY_CODE_SYSTEM); - include.addConcept().setCode("ParentA"); - include.addConcept().setCode("childAA"); - include.addConcept().setCode("childAAA"); - - include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("Parent B"); - - ValueSet result = myValueSetDao.expand(vs, null); - - String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); - ourLog.info(encoded); - - ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAA", "ParentB")); - - int idx = codes.indexOf("childAA"); - assertEquals("childAA", result.getExpansion().getContains().get(idx).getCode()); - assertEquals("Child AA", result.getExpansion().getContains().get(idx).getDisplay()); - assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem()); - } - - @Test - public void testIndexingIsDeferredForLargeCodeSystems() { - myDaoConfig.setDeferIndexingForCodesystemsOfSize(1); - - myTermSvc.setProcessDeferred(false); - - createExternalCsAndLocalVs(); - - ValueSet vs = new ValueSet(); - ConceptSetComponent include = vs.getCompose().addInclude(); - include.setSystem(URL_MY_CODE_SYSTEM); - - include.addFilter().setProperty("display").setOp(FilterOperator.ISA).setValue("ParentA"); - - ValueSet result = myValueSetDao.expand(vs, null); - String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); - ourLog.info(encoded); - - assertEquals(0, result.getExpansion().getContains().size()); - - myTermSvc.setProcessDeferred(true); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - - result = myValueSetDao.expand(vs, null); - encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); - ourLog.info(encoded); - assertEquals(4, result.getExpansion().getContains().size()); - - } - @Test public void testExpandWithExcludeInExternalValueSet() { createExternalCsAndLocalVs(); @@ -395,8 +197,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { ourLog.info(encoded); ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("ParentA", "ParentB", "childAB", "childAAB")); - + assertThat(codes, containsInAnyOrder("ParentA", "ParentB", "childAB", "childAAB", "ParentC", "childBA", "childCA")); } @Test @@ -421,34 +222,6 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { } } - @Test - public void testExpandWithSystemAndCodesAndFilterKeywordInLocalValueSet() { - createLocalCsAndVs(); - - ValueSet vs = new ValueSet(); - ConceptSetComponent include = vs.getCompose().addInclude(); - include.setSystem(URL_MY_CODE_SYSTEM); -// include.addConcept().setCode("A"); -// include.addConcept().setCode("AA"); -// include.addConcept().setCode("AAA"); -// include.addConcept().setCode("AB"); - - include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("AAA"); - - ValueSet result = myValueSetDao.expand(vs, null); - - String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); - ourLog.info(encoded); - - ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("AAA")); - - assertEquals("AAA", result.getExpansion().getContains().get(0).getCode()); - assertEquals("Code AAA", result.getExpansion().getContains().get(0).getDisplay()); - assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(0).getSystem()); -// - } - @Test public void testExpandWithNoResultsInLocalValueSet1() { createLocalCsAndVs(); @@ -484,14 +257,202 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { } } - private ArrayList toCodesContains(List theContains) { - ArrayList retVal = new ArrayList(); - for (ValueSetExpansionContainsComponent next : theContains) { - retVal.add(next.getCode()); - } - return retVal; + + + @Test + public void testExpandWithSystemAndCodesAndFilterInExternalValueSet() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addConcept().setCode("ParentA"); + include.addConcept().setCode("childAA"); + include.addConcept().setCode("childAAA"); + + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("Parent B"); + + ValueSet result = myValueSetDao.expand(vs, null); + + String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAA", "ParentB")); + + int idx = codes.indexOf("childAA"); + assertEquals("childAA", result.getExpansion().getContains().get(idx).getCode()); + assertEquals("Child AA", result.getExpansion().getContains().get(idx).getDisplay()); + assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem()); } + @Test + public void testExpandWithDisplayInExternalValueSetFuzzyMatching() { + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("parent a"); + ValueSet result = myValueSetDao.expand(vs, null); + String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentA")); + + vs = new ValueSet(); + include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("pare"); + result = myValueSetDao.expand(vs, null); + encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("ParentA", "ParentB", "ParentC")); + + vs = new ValueSet(); + include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("display:exact").setOp(FilterOperator.EQUAL).setValue("pare"); + result = myValueSetDao.expand(vs, null); + encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, empty()); + + } + + @Test + public void testExpandWithSystemAndCodesAndFilterKeywordInLocalValueSet() { + createLocalCsAndVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); +// include.addConcept().setCode("A"); +// include.addConcept().setCode("AA"); +// include.addConcept().setCode("AAA"); +// include.addConcept().setCode("AB"); + + include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("AAA"); + + ValueSet result = myValueSetDao.expand(vs, null); + + String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("AAA")); + + assertEquals("AAA", result.getExpansion().getContains().get(0).getCode()); + assertEquals("Code AAA", result.getExpansion().getContains().get(0).getDisplay()); + assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(0).getSystem()); +// + } + + @Test + public void testExpandWithSystemAndCodesInLocalValueSet() { + createLocalCsAndVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addConcept().setCode("A"); + include.addConcept().setCode("AA"); + include.addConcept().setCode("AAA"); + include.addConcept().setCode("AB"); + + ValueSet result = myValueSetDao.expand(vs, null); + + String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertThat(codes, containsInAnyOrder("A", "AA", "AAA", "AB")); + + int idx = codes.indexOf("AAA"); + assertEquals("AAA", result.getExpansion().getContains().get(idx).getCode()); + assertEquals("Code AAA", result.getExpansion().getContains().get(idx).getDisplay()); + assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem()); +// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical"); +// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical"); +// + } + + @Test + public void testIndexingIsDeferredForLargeCodeSystems() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(1); + + myTermSvc.setProcessDeferred(false); + + createExternalCsAndLocalVs(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + + ValueSet result = myValueSetDao.expand(vs, null); + String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + + assertEquals(0, result.getExpansion().getContains().size()); + + myTermSvc.setProcessDeferred(true); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + myTermSvc.saveDeferred(); + + vs = new ValueSet(); + include = vs.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + result = myValueSetDao.expand(vs, null); + encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result); + ourLog.info(encoded); + + assertEquals(4, result.getExpansion().getContains().size()); + + assertThat(encoded, containsStringIgnoringCase("")); + } + + /** + * Can't currently abort costly + */ + @Test + @Ignore + public void testRefuseCostlyExpansionFhirCodesystem() { + createLocalCsAndVs(); + myDaoConfig.setMaximumExpansionSize(1); + + SearchParameterMap params = new SearchParameterMap(); + params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); + try { + myAuditEventDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("", e.getMessage()); + } + } + + @Test + public void testRefuseCostlyExpansionLocalCodesystem() { + createLocalCsAndVs(); + myDaoConfig.setMaximumExpansionSize(1); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.ABOVE)); + try { + myObservationDao.search(params); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Expansion of ValueSet produced too many codes (maximum 1) - Operation aborted!", e.getMessage()); + } + } @Test public void testSearchCodeAboveLocalCodesystem() { @@ -519,16 +480,23 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { } - @Before - public void before() { - myDaoConfig.setMaximumExpansionSize(5000); + @Test + public void testSearchCodeBelowAndAboveUnknownCodeSystem() { + + SearchParameterMap params = new SearchParameterMap(); + + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + } - @After - public void after() { - myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); - } - + @Test public void testSearchCodeBelowLocalCodesystem() { createLocalCsAndVs(); @@ -555,52 +523,134 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { } - private void createLocalCsAndVs() { - //@formatter:off - CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl(URL_MY_CODE_SYSTEM); - codeSystem.setContent(CodeSystemContentMode.COMPLETE); - codeSystem - .addConcept().setCode("A").setDisplay("Code A") - .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") - .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) - ) - .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); - codeSystem - .addConcept().setCode("B").setDisplay("Code B") - .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) - .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); - //@formatter:on - myCodeSystemDao.create(codeSystem, mySrd); + @Test + @Ignore + public void testSearchCodeInEmptyValueSet() { + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(URL_MY_VALUE_SET); + myValueSetDao.create(valueSet, mySrd); + + Observation obsAA = new Observation(); + obsAA.setStatus(ObservationStatus.FINAL); + obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA"); + myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless(); - createLocalVs(codeSystem); + Observation obsBA = new Observation(); + obsBA.setStatus(ObservationStatus.FINAL); + obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); + myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.setStatus(ObservationStatus.FINAL); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params; + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + params.add(Observation.SP_STATUS, new TokenParam(null, "final")); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + } + + @Test + public void testSearchCodeInExternalCodesystem() { + createExternalCsAndLocalVs(); + + Observation obsPA = new Observation(); + obsPA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("ParentA"); + IIdType idPA = myObservationDao.create(obsPA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsAAA = new Observation(); + obsAAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAA"); + IIdType idAAA = myObservationDao.create(obsAAA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsAAB = new Observation(); + obsAAB.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAB"); + IIdType idAAB = myObservationDao.create(obsAAB, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAAA.getValue(), idAAB.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue(), idAAA.getValue(), idAAB.getValue())); + + } + + @Test + public void testSearchCodeInFhirCodesystem() { + createLocalCsAndVs(); + + AuditEvent aeIn1 = new AuditEvent(); + aeIn1.getType().setSystem("http://nema.org/dicom/dicm").setCode("110102"); + IIdType idIn1 = myAuditEventDao.create(aeIn1, mySrd).getId().toUnqualifiedVersionless(); + + AuditEvent aeIn2 = new AuditEvent(); + aeIn2.getType().setSystem("http://hl7.org/fhir/audit-event-type").setCode("rest"); + IIdType idIn2 = myAuditEventDao.create(aeIn2, mySrd).getId().toUnqualifiedVersionless(); + + AuditEvent aeOut1 = new AuditEvent(); + aeOut1.getType().setSystem("http://example.com").setCode("foo"); + IIdType idOut1 = myAuditEventDao.create(aeOut1, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAuditEventDao.search(params)), containsInAnyOrder(idIn1.getValue(), idIn2.getValue())); + + params = new SearchParameterMap(); + params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/v3-PurposeOfUse").setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myAuditEventDao.search(params)), empty()); + } + + @Test + public void testSearchCodeInLocalCodesystem() { + createLocalCsAndVs(); + + Observation obsAA = new Observation(); + obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA"); + IIdType idAA = myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsBA = new Observation(); + obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); + IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAA.getValue(), idBA.getValue())); + } - private void createLocalVs(CodeSystem codeSystem) { - ValueSet valueSet = new ValueSet(); - valueSet.setUrl(URL_MY_VALUE_SET); - valueSet.getCompose().addInclude().setSystem(codeSystem.getUrl()); - myValueSetDao.create(valueSet, mySrd); + private ArrayList toCodesContains(List theContains) { + ArrayList retVal = new ArrayList(); + for (ValueSetExpansionContainsComponent next : theContains) { + retVal.add(next.getCode()); + } + return retVal; } - @Test - public void testCodeSystemCreateDuplicateFails() { - CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl(URL_MY_CODE_SYSTEM); - codeSystem.setContent(CodeSystemContentMode.COMPLETE); - IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); - - codeSystem = new CodeSystem(); - codeSystem.setUrl(URL_MY_CODE_SYSTEM); - codeSystem.setContent(CodeSystemContentMode.COMPLETE); - try { - 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()); - } + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java index fa2894d7343..3976b73c99b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java @@ -26,7 +26,6 @@ import org.junit.Test; import org.springframework.transaction.annotation.Transactional; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { @@ -62,7 +61,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); } @@ -75,7 +74,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure--expiration", result.getDisplay()); } @@ -89,7 +88,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -103,7 +102,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -117,7 +116,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = new StringType("Systolic blood pressure at First encounter"); Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -132,7 +131,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { Coding coding = null; CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -146,7 +145,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept); + ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -155,7 +154,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { public void testExpandById() throws IOException { String resp; - ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null); + ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); ourLog.info(resp); assertThat(resp, containsString("")); @@ -176,7 +175,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { * Filter with display name */ - expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic")); + expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic"), mySrd); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); ourLog.info(resp); //@formatter:off diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2ValueSetTest.java index 36a2a3c7616..6da4f7e8448 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2ValueSetTest.java @@ -24,7 +24,6 @@ import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class ResourceProviderDstu2ValueSetTest extends BaseResourceProviderDstu2Test { @@ -251,7 +250,7 @@ public class ResourceProviderDstu2ValueSetTest extends BaseResourceProviderDstu2 expanded = (ValueSet) respParam.getParameter().get(0).getResource(); //@formatter:on - expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic")); + expanded = myValueSetDao.expand(myExtensionalVsId, ("systolic"), mySrd); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); ourLog.info(resp); //@formatter:off diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index a5e0330712f..8468e39d973 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -1,9 +1,12 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest.*; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.stringContainsInOrder; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -12,25 +15,33 @@ import java.io.IOException; import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeType; -import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; +import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; +import ca.uhn.fhir.jpa.entity.ResourceTable; +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.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.util.TestUtil; public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3ValueSetTest.class); private IIdType myExtensionalVsId; + private IIdType myLocalValueSetId; + private ValueSet myLocalVs; @AfterClass public static void afterClassClearContext() { @@ -78,6 +89,140 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 } + @Test + public void testExpandLocalVsAgainstExternalCs() throws IOException { + createExternalCsAndLocalVs(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onInstance(myLocalValueSetId) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + @Test + public void testExpandLocalVsWithUnknownCode() throws IOException { + createExternalCsAndLocalVsWithUnknownCode(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + try { + ourClient + .operation() + .onInstance(myLocalValueSetId) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: Invalid filter criteria - code does not exist: {http://example.com/my_code_system}childFOOOOOOO", e.getMessage()); + } + //@formatter:on + } + + @Test + public void testExpandInlineVsAgainstBuiltInCs() throws IOException { + createLocalVsPointingAtBuiltInCodeSystem(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", myLocalVs) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + } + + @Test + public void testExpandLocalVsAgainstBuiltInCs() throws IOException { + createLocalVsPointingAtBuiltInCodeSystem(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onInstance(myLocalValueSetId) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + } + + @Test + public void testExpandLocalVsCanonicalAgainstExternalCs() throws IOException { + createExternalCsAndLocalVs(); + assertNotNull(myLocalValueSetId); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "identifier", new UriType(URL_MY_VALUE_SET)) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + @Test + public void testExpandInlineVsAgainstExternalCs() throws IOException { + createExternalCsAndLocalVs(); + assertNotNull(myLocalVs); + myLocalVs.setId(""); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", myLocalVs) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + @Test public void testExpandByIdWithFilter() throws IOException { @@ -231,4 +376,102 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 assertEquals(true, ((BooleanType)respParam.getParameter().get(0).getValue()).booleanValue()); } + + + + + private CodeSystem createExternalCs() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA").setDisplay("Child AA"); + parentA.addChild(childAA, RelationshipTypeEnum.ISA); + + TermConcept childAAA = new TermConcept(cs, "childAAA").setDisplay("Child AAA"); + childAA.addChild(childAAA, RelationshipTypeEnum.ISA); + + TermConcept childAAB = new TermConcept(cs, "childAAB").setDisplay("Child AAB"); + childAA.addChild(childAAB, RelationshipTypeEnum.ISA); + + TermConcept childAB = new TermConcept(cs, "childAB").setDisplay("Child AB"); + parentA.addChild(childAB, RelationshipTypeEnum.ISA); + + TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); + cs.getConcepts().add(parentB); + + myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, cs); + return codeSystem; + } + + private void createExternalCsAndLocalVs() { + CodeSystem codeSystem = createExternalCs(); + + createLocalVs(codeSystem); + } + + private void createExternalCsAndLocalVsWithUnknownCode() { + CodeSystem codeSystem = createExternalCs(); + + createLocalVsWithUnknownCode(codeSystem); + } + + private void createLocalCsAndVs() { + //@formatter:off + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") + .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) + ) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + codeSystem + .addConcept().setCode("B").setDisplay("Code B") + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); + //@formatter:on + myCodeSystemDao.create(codeSystem, mySrd); + + createLocalVs(codeSystem); + } + + + private void createLocalVs(CodeSystem codeSystem) { + myLocalVs = new ValueSet(); + myLocalVs.setUrl(URL_MY_VALUE_SET); + ConceptSetComponent include = myLocalVs.getCompose().addInclude(); + include.setSystem(codeSystem.getUrl()); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("childAA"); + myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); + } + + private void createLocalVsWithUnknownCode(CodeSystem codeSystem) { + myLocalVs = new ValueSet(); + myLocalVs.setUrl(URL_MY_VALUE_SET); + ConceptSetComponent include = myLocalVs.getCompose().addInclude(); + include.setSystem(codeSystem.getUrl()); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("childFOOOOOOO"); + myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); + } + + private void createLocalVsPointingAtBuiltInCodeSystem() { + myLocalVs = new ValueSet(); + myLocalVs.setUrl(URL_MY_VALUE_SET); + ConceptSetComponent include = myLocalVs.getCompose().addInclude(); + include.setSystem("http://hl7.org/fhir/v3/MaritalStatus"); + myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java index 7dc0518d287..aaa04e9926b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderDstu3Test.java @@ -367,6 +367,19 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test { } } + @Test + public void testMarkResourcesForReindexing() throws Exception { + HttpGet get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing"); + CloseableHttpResponse http = ourHttpClient.execute(get); + try { + String output = IOUtils.toString(http.getEntity().getContent()); + ourLog.info(output); + assertEquals(200, http.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(http);; + } + } + @Transactional(propagation = Propagation.NEVER) @Test public void testSuggestKeywords() throws Exception { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java index 5d1e08af0db..b4e41aa2c3c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java @@ -30,10 +30,6 @@ public class TerminologyLoaderSvcIntegrationTest extends BaseJpaDstu3Test { @Test @Ignore public void testLoadAndStoreSnomedCt() { - Map files = new HashMap(); - files.put(TerminologyLoaderSvc.SCT_FILE_CONCEPT, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Concept_Full_INT_20160131.txt")); - files.put(TerminologyLoaderSvc.SCT_FILE_DESCRIPTION, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Description_Full-en_INT_20160131.txt")); - files.put(TerminologyLoaderSvc.SCT_FILE_RELATIONSHIP, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Relationship_Full_INT_20160131.txt")); // myLoader.processSnomedCtFiles(files, mySrd); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java index f8f9f9b1f3c..3a07b2b0578 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcTest.java @@ -1,17 +1,17 @@ package ca.uhn.fhir.jpa.term; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInRelativeOrder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -import static org.mockito.Mockito.any; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -25,6 +25,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; import org.junit.AfterClass; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -79,6 +80,21 @@ public class TerminologyLoaderSvcTest { mySvc.loadLoinc(list(bos1.toByteArray(), bos2.toByteArray()), details); } + + /** + * This is just for trying stuff, it won't run without + * local files external to the git repo + */ + @Ignore + @Test + public void testLoadSnomedCtAgainstRealFile() throws Exception { + byte[] bytes = IOUtils.toByteArray(new FileInputStream("/Users/james/Downloads/SnomedCT_Release_INT_20160131_Full.zip")); + + RequestDetails details = mock(RequestDetails.class); + mySvc.loadSnomedCt(list(bytes), details); + } + + @Test public void testLoadSnomedCt() throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); diff --git a/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt b/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt index 43be7c43d81..9ad27019224 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt +++ b/hapi-fhir-jpaserver-base/src/test/resources/sct/sct2_Concept_Full_INT_20160131.txt @@ -17,3 +17,4 @@ id effectiveTime active moduleId definitionStatusId 207527008 20020131 1 900000000000207008 900000000000074008 207527008 20040731 1 900000000000207008 900000000000073002 207527008 20090731 0 900000000000207008 900000000000074008 +404684003 20040131 1 900000000000207008 900000000000074008 diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/HapiWorkerContext.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/HapiWorkerContext.java index d5ec543d0c6..cab4c0ea645 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/HapiWorkerContext.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/HapiWorkerContext.java @@ -67,6 +67,8 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander ValueSetExpansionOutcome vso; try { vso = getExpander().expand(theSource); + } catch (InvalidRequestException e) { + throw e; } catch (Exception e) { throw new InternalErrorException(e); } @@ -125,7 +127,9 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander @Override public ValueSetExpander getExpander() { - return new ValueSetExpanderSimple(this, this); + ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this, this); + retVal.setMaxExpansionSize(Integer.MAX_VALUE); + return retVal; } @Override diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/terminologies/ValueSetExpanderSimple.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/terminologies/ValueSetExpanderSimple.java index 12091ec9b18..a80d8a9d78b 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/terminologies/ValueSetExpanderSimple.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/terminologies/ValueSetExpanderSimple.java @@ -70,8 +70,10 @@ public class ValueSetExpanderSimple implements ValueSetExpander { private Set excludeKeys = new HashSet(); private ValueSetExpanderFactory factory; private ValueSet focus; + private int maxExpansionSize = 500; private Map map = new HashMap(); + private int total; public ValueSetExpanderSimple(IWorkerContext context, ValueSetExpanderFactory factory) { super(); @@ -79,6 +81,10 @@ public class ValueSetExpanderSimple implements ValueSetExpander { this.factory = factory; } + public void setMaxExpansionSize(int theMaxExpansionSize) { + maxExpansionSize = theMaxExpansionSize; + } + private void addCode(String system, String code, String display) { ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); n.setSystem(system); @@ -101,7 +107,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander { } private void addCodes(ValueSetExpansionComponent expand, List params) throws ETooCostly { - if (expand.getContains().size() > 500) + if (expand.getContains().size() > maxExpansionSize) throw new ETooCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")"); for (ValueSetExpansionParameterComponent p : expand.getParameter()) { if (!existsInParams(params, p.getName(), p.getValue())) @@ -111,6 +117,8 @@ public class ValueSetExpanderSimple implements ValueSetExpander { for (ValueSetExpansionContainsComponent c : expand.getContains()) { addCode(c.getSystem(), c.getCode(), c.getDisplay()); } + + total = expand.getTotal(); } private void excludeCode(String theSystem, String theCode) { @@ -171,6 +179,11 @@ public class ValueSetExpanderSimple implements ValueSetExpander { focus.getExpansion().getContains().add(c); } } + + if (total > 0) { + focus.getExpansion().setTotal(total); + } + return new ValueSetExpansionOutcome(focus, null); } catch (RuntimeException e) { // TODO: we should put something more specific instead of just Exception below, since