Merge pull request #2094 from jamesagnew/ft_2020_09_16_codesystem_validate

Support CodeSystem/$validate-code for R4, R5
This commit is contained in:
Frank Tao 2020-09-23 15:34:38 -04:00 committed by GitHub
commit 404abb2dcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 998 additions and 101 deletions

View File

@ -47,6 +47,8 @@ public interface IFhirResourceDaoCodeSystem<T extends IBaseResource, CD, CC> ext
SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails); SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails);
IValidationSupport.CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl, IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails);
class SubsumesResult { class SubsumesResult {
private final ConceptSubsumptionOutcome myOutcome; private final ConceptSubsumptionOutcome myOutcome;

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -56,6 +57,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions; import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static ca.uhn.fhir.jpa.util.LogicUtil.multiXor; import static ca.uhn.fhir.jpa.util.LogicUtil.multiXor;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
@ -312,4 +314,10 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept); return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
} }
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl, IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode,
IPrimitiveType<String> theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequestDetails) {
throw new UnsupportedOperationException();
}
} }

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
@ -169,4 +170,10 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
return retVal; return retVal;
} }
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl, IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode,
IPrimitiveType<String> theDisplay, Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
throw new UnsupportedOperationException();
}
} }

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
@ -50,6 +51,8 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> { public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> {
@ -164,5 +167,13 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
return retVal; return retVal;
} }
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl,
IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay,
Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.codeSystemValidateCode(theCodeSystemId, toStringOrNull(theCodeSystemUrl), toStringOrNull(theVersion), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
}
} }

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.r5;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
@ -51,6 +52,8 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> { public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> {
@ -166,5 +169,11 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
return retVal; return retVal;
} }
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl,
IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay,
Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.codeSystemValidateCode(theCodeSystemId, toStringOrNull(theCodeSystemUrl), toStringOrNull(theVersion), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
}
} }

View File

@ -1,5 +1,19 @@
package ca.uhn.fhir.jpa.provider.r4; package ca.uhn.fhir.jpa.provider.r4;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -23,20 +37,11 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<CodeSystem> { public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<CodeSystem> {
@ -98,4 +103,36 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
endRequest(theServletRequest); endRequest(theServletRequest);
} }
} }
/**
* $validate-code operation
*/
@SuppressWarnings("unchecked")
@Operation(name = JpaConstants.OPERATION_VALIDATE_CODE, idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "version", min = 0, max = 1) StringType theVersion,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@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,
RequestDetails theRequestDetails
) {
startRequest(theServletRequest);
try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IValidationSupport.CodeValidationResult result = dao.validateCode(theId, theCodeSystemUrl, theVersion, theCode, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);
}
}
} }

View File

@ -23,9 +23,13 @@ package ca.uhn.fhir.jpa.provider.r5;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeType; import org.hl7.fhir.r5.model.CodeType;
@ -98,4 +102,36 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5<C
endRequest(theServletRequest); endRequest(theServletRequest);
} }
} }
/**
* $validate-code operation
*/
@SuppressWarnings("unchecked")
@Operation(name = JpaConstants.OPERATION_VALIDATE_CODE, idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "version", min = 0, max = 1) StringType theVersion,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@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,
RequestDetails theRequestDetails
) {
startRequest(theServletRequest);
try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IValidationSupport.CodeValidationResult result = dao.validateCode(theId, theCodeSystemUrl, theVersion, theCode, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);
}
}
} }

View File

@ -1,5 +1,98 @@
package ca.uhn.fhir.jpa.term; package ca.uhn.fhir.jpa.term;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Fetch;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.TermQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.support.TransactionTemplate;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -85,97 +178,10 @@ import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.TermQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseTermReadSvcImpl implements ITermReadSvc { public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250; public static final int DEFAULT_FETCH_SIZE = 250;
private static final int SINGLE_FETCH_SIZE = 1;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseTermReadSvcImpl.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseTermReadSvcImpl.class);
private static final ValueSetExpansionOptions DEFAULT_EXPANSION_OPTIONS = new ValueSetExpansionOptions(); private static final ValueSetExpansionOptions DEFAULT_EXPANSION_OPTIONS = new ValueSetExpansionOptions();
private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L); private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L);
@ -2555,5 +2561,120 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return ourLastResultsFromTranslationWithReverseCache; return ourLastResultsFromTranslationWithReverseCache;
} }
@Override
@Transactional
public CodeValidationResult codeSystemValidateCode(IIdType theCodeSystemId, String theCodeSystemUrl, String theVersion, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept);
boolean haveCodeableConcept = codeableConcept != null && codeableConcept.getCoding().size() > 0;
Coding coding = toCanonicalCoding(theCoding);
boolean haveCoding = coding != null && coding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate.");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = isNotBlank(theCodeSystemUrl);
String codeSystemUrl;
if (theCodeSystemId != null) {
IBaseResource codeSystem = myDaoRegistry.getResourceDao("CodeSystem").read(theCodeSystemId);
codeSystemUrl = CommonCodeSystemsTerminologyService.getCodeSystemUrl(codeSystem);
} else if (haveIdentifierParam) {
codeSystemUrl = theCodeSystemUrl;
} else {
throw new InvalidRequestException("Either CodeSystem ID or CodeSystem identifier must be provided. Unable to validate.");
}
String code = theCode;
String version = theVersion;
String display = theDisplay;
if (haveCodeableConcept) {
for (int i = 0; i < codeableConcept.getCoding().size(); i++) {
Coding nextCoding = codeableConcept.getCoding().get(i);
if (nextCoding.hasSystem()) {
if (!codeSystemUrl.equalsIgnoreCase(nextCoding.getSystem())) {
throw new InvalidRequestException("Coding.system '" + nextCoding.getSystem() + "' does not equal with CodeSystem.url '" + theCodeSystemUrl + "'. Unable to validate.");
}
codeSystemUrl = nextCoding.getSystem();
}
code = nextCoding.getCode();
display = nextCoding.getDisplay();
CodeValidationResult nextValidation = codeSystemValidateCode(codeSystemUrl, version, code, display);
if (nextValidation.isOk() || i == codeableConcept.getCoding().size() - 1) {
return nextValidation;
}
}
} else if (haveCoding) {
if (coding.hasSystem()) {
if (!codeSystemUrl.equalsIgnoreCase(coding.getSystem())) {
throw new InvalidRequestException("Coding.system '" + coding.getSystem() + "' does not equal with CodeSystem.url '" + theCodeSystemUrl + "'. Unable to validate.");
}
codeSystemUrl = coding.getSystem();
}
code = coding.getCode();
display = coding.getDisplay();
}
return codeSystemValidateCode(codeSystemUrl, version, code, display);
}
@SuppressWarnings("unchecked")
private CodeValidationResult codeSystemValidateCode(String theCodeSystemUrl, String theCodeSystemVersion, String theCode, String theDisplay) {
CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TermConcept> query = criteriaBuilder.createQuery(TermConcept.class);
Root<TermConcept> root = query.from(TermConcept.class);
Fetch<TermCodeSystemVersion, TermConcept> systemVersionFetch = root.fetch("myCodeSystem", JoinType.INNER);
Join<TermCodeSystemVersion, TermConcept> systemVersionJoin = (Join<TermCodeSystemVersion, TermConcept>)systemVersionFetch;
Fetch<TermCodeSystem, TermCodeSystemVersion> systemFetch = systemVersionFetch.fetch("myCodeSystem", JoinType.INNER);
Join<TermCodeSystem, TermCodeSystemVersion> systemJoin = (Join<TermCodeSystem, TermCodeSystemVersion>)systemFetch;
ArrayList<Predicate> predicates = new ArrayList<>();
if (isNotBlank(theCode)) {
predicates.add(criteriaBuilder.equal(root.get("myCode"), theCode));
}
if (isNotBlank(theDisplay)) {
predicates.add(criteriaBuilder.equal(root.get("myDisplay"), theDisplay));
}
if (isNoneBlank(theCodeSystemUrl)) {
predicates.add(criteriaBuilder.equal(systemJoin.get("myCodeSystemUri"), theCodeSystemUrl));
}
if (isNoneBlank(theCodeSystemVersion)) {
predicates.add(criteriaBuilder.equal(systemVersionJoin.get("myCodeSystemVersionId"), theCodeSystemVersion));
} else {
query.orderBy(criteriaBuilder.desc(root.get("myUpdated")));
}
Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
query.where(outerPredicate);
final TypedQuery<TermConcept> typedQuery = myEntityManager.createQuery(query.select(root));
org.hibernate.query.Query<TermConcept> hibernateQuery = (org.hibernate.query.Query<TermConcept>) typedQuery;
hibernateQuery.setFetchSize(SINGLE_FETCH_SIZE);
List<TermConcept> resultsList = hibernateQuery.getResultList();
if (!resultsList.isEmpty()) {
TermConcept concept = resultsList.get(0);
return new CodeValidationResult().setCode(concept.getCode()).setDisplay(concept.getDisplay());
}
if (isBlank(theDisplay))
return createFailureCodeValidationResult(theCodeSystemUrl, theCode);
else
return createFailureCodeValidationResult(theCodeSystemUrl, theCode, " - Concept Display : " + theDisplay);
}
} }

View File

@ -126,4 +126,9 @@ public interface ITermReadSvc extends IValidationSupport {
*/ */
boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet); boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet);
/**
* Version independent
*/
CodeValidationResult codeSystemValidateCode(IIdType theCodeSystemId, String theValueSetUrl, String theVersion, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
} }

View File

@ -6,11 +6,9 @@ import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest;
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -332,6 +330,19 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
return ourCtx.newJsonParser().parseResource(theType, loadResource(theFilename)); return ourCtx.newJsonParser().parseResource(theType, loadResource(theFilename));
} }
@Test
public void testValidateCodeOperation() {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType("https://url"));
inParams.addParameter().setName("code").setValue(new CodeType("1"));
try {
ourClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[CodeSystem/$validate-code] with parameters [[]]", e.getMessage());
}
}
} }

View File

@ -0,0 +1,560 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import javax.annotation.Nonnull;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class ResourceProviderR4CodeSystemValidationTest extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4CodeSystemValidationTest.class);
private IIdType myCsId;
private static final String CS_ACMS_URL = "http://acme.org";
@BeforeEach
@Transactional
public void before02() throws IOException {
loadAndPersistCodeSystem();
}
private void loadAndPersistCodeSystem() throws IOException {
CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
codeSystem.setId("CodeSystem/cs");
persistCodeSystem(codeSystem);
}
private void persistCodeSystem(CodeSystem theCodeSystem) {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
}
});
myCodeSystemDao.readEntity(myCsId, null).getId();
}
@Test
public void testValidateCodeFoundByCode() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeNotFoundByCode() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5-a"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeMatchDisplay() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
inParams.addParameter().setName("display").setValue(new StringType("Systolic blood pressure.inspiration - expiration"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeNotMatchDisplay() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
inParams.addParameter().setName("display").setValue(new StringType("Old Systolic blood pressure.inspiration - expiration"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5 - Concept Display : Old Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeWithoutUrl() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Either CodeSystem ID or CodeSystem identifier must be provided. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodeWithId() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
Parameters respParam = myClient.operation().onInstance(myCsId).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeWithoutCodeOrCodingOrCodeableConcept() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("display").setValue(new StringType("Systolic blood pressure.inspiration - expiration"));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: No code, coding, or codeableConcept provided to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeWithCodeAndCoding() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-1")));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: $validate-code can only validate (code) OR (coding) OR (codeableConcept)",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodingWithUrlNotMatch() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setSystem("http://url2")));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Coding.system 'http://url2' does not equal with CodeSystem.url 'http://acme.org'. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCoding() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5")));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodingWithSystem() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setSystem(CS_ACMS_URL)));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodingUrlNotMatch() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setSystem("http://url2")));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Coding.system 'http://url2' does not equal with CodeSystem.url 'http://acme.org'. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodingWithDisplay() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setDisplay("Systolic blood pressure.inspiration - expiration")));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeNotFoundByCoding() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5-a")));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConcept() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithSystem() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL);
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithDisplay() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeNotFoundByCodeableConcept() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5-a");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptUrlNotMatch() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem("http://url2").setDisplay("Systolic blood pressure.inspiration - expiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Coding.system 'http://url2' does not equal with CodeSystem.url 'http://acme.org'. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodeableConceptWithMultipleMatchedEntries() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
cc.addCoding().setCode("8451-7").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure--inspiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithMultipleMatchedFirstEntry() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
cc.addCoding().setCode("8451-7-a").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure--inspiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithMultipleMatchedSecondEntry() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5-a").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
cc.addCoding().setCode("8451-7").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure--inspiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure--inspiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeWithUrlAndVersion_v1() {
String url = "http://url";
createCodeSystem(url, "v1", "1", "Code v1 display");
createCodeSystem(url, "v2", "1", "Code v2 display");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(url));
inParams.addParameter().setName("version").setValue(new StringType("v1"));
inParams.addParameter().setName("code").setValue(new CodeType("1"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam));
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Code v1 display", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeWithUrlAndVersion_v2() {
String url = "http://url";
createCodeSystem(url, "v1", "1", "Code v1 display");
createCodeSystem(url, "v2", "1", "Code v2 display");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(url));
inParams.addParameter().setName("version").setValue(new StringType("v2"));
inParams.addParameter().setName("code").setValue(new CodeType("1"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam));
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Code v2 display", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeWithUrlAndVersion_noVersion() {
String url = "http://url";
createCodeSystem(url, "v1", "1", "Code v1 display");
createCodeSystem(url, "v2", "1", "Code v2 display");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(url));
inParams.addParameter().setName("code").setValue(new CodeType("1"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam));
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Code v2 display", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeWithUrlAndVersion_noVersion_null_v1() {
String url = "http://url";
createCodeSystem(url, null, "1", "Code v1 display");
createCodeSystem(url, "v2", "1", "Code v2 display");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(url));
inParams.addParameter().setName("code").setValue(new CodeType("1"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam));
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Code v2 display", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeWithUrlAndVersion_noVersion_null_v2() {
String url = "http://url";
createCodeSystem(url, "v1", "1", "Code v1 display");
createCodeSystem(url, null, "1", "Code v2 display");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(url));
inParams.addParameter().setName("code").setValue(new CodeType("1"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam));
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Code v2 display", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
private void createCodeSystem(String url, String version, String code, String display) {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(url).setVersion(version);
ConceptDefinitionComponent concept1 = codeSystem.addConcept();
concept1.setCode("1000").setDisplay("Code Dispaly 1000");
ConceptDefinitionComponent concept = codeSystem.addConcept();
concept.setCode(code).setDisplay(display);
ConceptDefinitionComponent concept2 = codeSystem.addConcept();
concept2.setCode("2000").setDisplay("Code Dispaly 2000");
ourLog.info("CodeSystem: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem));
myCodeSystemDao.create(codeSystem, mySrd);
}
}

View File

@ -0,0 +1,58 @@
package ca.uhn.fhir.jpa.provider.r5;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.UriType;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ResourceProviderR5CodeSystemTest extends BaseResourceProviderR5Test {
private static final Logger ourLog = LoggerFactory.getLogger(ResourceProviderR5CodeSystemTest.class);
@Test
public void testValidateCodeWithUrlAndVersion_v1() {
String url = "http://url";
createCodeSystem(url, "v1", "1", "Code v1 display");
createCodeSystem(url, "v2", "1", "Code v2 display");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(url));
inParams.addParameter().setName("version").setValue(new StringType("v1"));
inParams.addParameter().setName("code").setValue(new CodeType("1"));
inParams.addParameter().setName("display").setValue(new StringType("Code v1 display"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam));
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Code v1 display", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
private void createCodeSystem(String url, String version, String code, String display) {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(url).setVersion(version);
ConceptDefinitionComponent concept1 = codeSystem.addConcept();
concept1.setCode("1000").setDisplay("Code Dispaly 1000");
ConceptDefinitionComponent concept = codeSystem.addConcept();
concept.setCode(code).setDisplay(display);
ConceptDefinitionComponent concept2 = codeSystem.addConcept();
concept2.setCode("2000").setDisplay("Code Dispaly 2000");
ourLog.info("CodeSystem: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem));
myCodeSystemDao.create(codeSystem, mySrd);
}
}

View File

@ -332,6 +332,23 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
return url; return url;
} }
public static String getCodeSystemUrl(@Nonnull IBaseResource theCodeSystem) {
String url;
switch (theCodeSystem.getStructureFhirVersionEnum()) {
case R4: {
url = ((org.hl7.fhir.r4.model.CodeSystem) theCodeSystem).getUrl();
break;
}
case R5: {
url = ((org.hl7.fhir.r5.model.CodeSystem) theCodeSystem).getUrl();
break;
}
case DSTU3:
default:
throw new IllegalArgumentException("Can not handle version: " + theCodeSystem.getStructureFhirVersionEnum());
}
return url;
}
private static HashMap<String, String> buildUspsCodes() { private static HashMap<String, String> buildUspsCodes() {
HashMap<String, String> uspsCodes = new HashMap<>(); HashMap<String, String> uspsCodes = new HashMap<>();
uspsCodes.put("AK", "Alaska"); uspsCodes.put("AK", "Alaska");

View File

@ -5,6 +5,8 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
@ -13,6 +15,9 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.fail;
import javax.annotation.Nonnull;
public class CommonCodeSystemsTerminologyServiceTest { public class CommonCodeSystemsTerminologyServiceTest {
@ -114,4 +119,14 @@ public class CommonCodeSystemsTerminologyServiceTest {
assertEquals(null, cs); assertEquals(null, cs);
} }
@Test
public void testFetchCodeSystemUrlDstu3() {
try {
CommonCodeSystemsTerminologyService.getCodeSystemUrl(new org.hl7.fhir.dstu3.model.CodeSystem());
fail();
} catch (IllegalArgumentException e) {
assertEquals("Can not handle version: DSTU3", e.getMessage());
}
}
} }