Merge branch 'master' into spring-batch-integration

This commit is contained in:
Tadgh 2020-06-23 16:02:41 -07:00
commit bb7a773f41
78 changed files with 2530 additions and 763 deletions

View File

@ -25,6 +25,16 @@ jobs:
inputs: inputs:
targetType: 'inline' targetType: 'inline'
script: mkdir -p $(MAVEN_CACHE_FOLDER); pwd; ls -al $(MAVEN_CACHE_FOLDER) script: mkdir -p $(MAVEN_CACHE_FOLDER); pwd; ls -al $(MAVEN_CACHE_FOLDER)
- task: Maven@3
env:
JAVA_HOME_11_X64: /usr/local/openjdk-11
inputs:
goals: 'dependency:go-offline'
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# These are JVM options (and don't show up in the build logs)
mavenOptions: '-Xmx1024m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
jdkVersionOption: 1.11
- task: Maven@3 - task: Maven@3
env: env:
JAVA_HOME_11_X64: /usr/local/openjdk-11 JAVA_HOME_11_X64: /usr/local/openjdk-11

View File

@ -31,6 +31,7 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -327,13 +328,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} }
private String getConformanceResourceUrl(IBaseResource theResource) { private String getConformanceResourceUrl(IBaseResource theResource) {
String urlValueString = null; return getConformanceResourceUrl(getFhirContext(), theResource);
Optional<IBase> urlValue = getFhirContext().getResourceDefinition(theResource).getChildByName("url").getAccessor().getFirstValueOrNull(theResource);
if (urlValue.isPresent()) {
IPrimitiveType<?> urlValueType = (IPrimitiveType<?>) urlValue.get();
urlValueString = urlValueType.getValueAsString();
}
return urlValueString;
} }
private List<IBaseResource> parseBundle(InputStreamReader theReader) { private List<IBaseResource> parseBundle(InputStreamReader theReader) {
@ -346,6 +341,17 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} }
} }
@Nullable
public static String getConformanceResourceUrl(FhirContext theFhirContext, IBaseResource theResource) {
String urlValueString = null;
Optional<IBase> urlValue = theFhirContext.getResourceDefinition(theResource).getChildByName("url").getAccessor().getFirstValueOrNull(theResource);
if (urlValue.isPresent()) {
IPrimitiveType<?> urlValueType = (IPrimitiveType<?>) urlValue.get();
urlValueString = urlValueType.getValueAsString();
}
return urlValueString;
}
static <T extends IBaseResource> List<T> toList(Map<String, IBaseResource> theMap) { static <T extends IBaseResource> List<T> toList(Map<String, IBaseResource> theMap) {
ArrayList<IBaseResource> retVal = new ArrayList<>(theMap.values()); ArrayList<IBaseResource> retVal = new ArrayList<>(theMap.values());
return (List<T>) Collections.unmodifiableList(retVal); return (List<T>) Collections.unmodifiableList(retVal);

View File

@ -454,7 +454,14 @@ public class FhirTerser {
List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj); List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj);
if (values.isEmpty() && theCreate) { if (values.isEmpty() && theCreate) {
IBase value = nextDef.getChildByName(name).newInstance(); BaseRuntimeElementDefinition<?> childByName = nextDef.getChildByName(name);
Object arg = nextDef.getInstanceConstructorArguments();
IBase value;
if (arg != null) {
value = childByName.newInstance(arg);
} else {
value = childByName.newInstance();
}
nextDef.getMutator().addValue(theCurrentObj, value); nextDef.getMutator().addValue(theCurrentObj, value);
List<IBase> list = new ArrayList<>(); List<IBase> list = new ArrayList<>();
list.add(value); list.add(value);

View File

@ -20,28 +20,18 @@ package ca.uhn.fhir.jpa.demo;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.H2Dialect;
import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus;
import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import pl.allegro.tech.embeddedelasticsearch.EmbeddedElastic;
import pl.allegro.tech.embeddedelasticsearch.PopularProperties;
import javax.annotation.PreDestroy;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties; import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -110,55 +100,7 @@ public class CommonConfig {
extraProperties.put("hibernate.search.autoregister_listeners", "false"); extraProperties.put("hibernate.search.autoregister_listeners", "false");
} }
return configureElasticearch(extraProperties); return extraProperties;
}
private Properties configureElasticearch(Properties theExtraProperties) {
String elasticsearchHost = "localhost";
String elasticsearchUserId = "";
String elasticsearchPassword = "";
int elasticsearchPort = embeddedElasticSearch().getHttpPort();
new ElasticsearchHibernatePropertiesBuilder()
.setDebugRefreshAfterWrite(true)
.setDebugPrettyPrintJsonLog(true)
.setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy.CREATE)
.setIndexManagementWaitTimeoutMillis(10000)
.setRequiredIndexStatus(ElasticsearchIndexStatus.YELLOW)
.setRestUrl("http://" + elasticsearchHost + ":" + elasticsearchPort)
.setUsername(elasticsearchUserId)
.setPassword(elasticsearchPassword)
.apply(theExtraProperties);
return theExtraProperties;
}
@Bean
public EmbeddedElastic embeddedElasticSearch() {
String ELASTIC_VERSION = "6.5.4";
EmbeddedElastic embeddedElastic = null;
try {
embeddedElastic = EmbeddedElastic.builder()
.withElasticVersion(ELASTIC_VERSION)
.withSetting(PopularProperties.TRANSPORT_TCP_PORT, 0)
.withSetting(PopularProperties.HTTP_PORT, 0)
.withSetting(PopularProperties.CLUSTER_NAME, UUID.randomUUID())
.withStartTimeout(60, TimeUnit.SECONDS)
.build()
.start();
} catch (IOException | InterruptedException e) {
throw new ConfigurationException(e);
}
return embeddedElastic;
}
@PreDestroy
public void stop() {
embeddedElasticSearch().stop();
} }
@Bean @Bean

View File

@ -1,5 +1,5 @@
--- ---
type: fix type: fix
issue: 1895 issue: 1895
title: "HAPI FHIR 5.0.0 introduced a regressin in JPA validator performance, where a number of unnecessary database lookups title: "HAPI FHIR 5.0.0 introduced a regression in JPA validator performance, where a number of unnecessary database lookups
were introduced. This has been corrected." were introduced. This has been corrected."

View File

@ -0,0 +1,4 @@
---
type: add
issue: 1917
title: "The LOINC importer has been updated to support the file format used by the LOINC 2.68 release."

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1932
title: "A new configuration bean called ValidationSettings has been added to the JPA server. This can be used
to enable validation of reference target resources if necessary."

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1933
title: "`BaseValidationSupportWrapper.expandValueSet(...)` and `ValidationSupportChain.expandValueSet(...)` were
incorrectly replacing expansion options (i.e. offset and count) with `null`, causing these parameters to be
ignored when invoking the `ValueSet$expand` operation. This has been corrected."

View File

@ -0,0 +1,8 @@
---
type: add
issue: 1934
title: "The **RemoteTerminologyServiceValidationSupport** validation support module, which is used to connect to
external/remote terminology services, has been significantly enhanced to provide testing for supported
CodeSystems and ValueSets. It will also now validate codes in fields that are not bound to a specific
ValueSet."

View File

@ -0,0 +1,6 @@
---
type: perf
issue: 1937
title: Due to an inefficient SQL statement when performing searches with large numbers of _revincludes where the resources
have tags, a large number of database roundtrips were produced when searching. This has been streamlined, greatly improving
the response times for some searches.

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1939
title: "The JPA server is now able to support _has queries where the linked search expression on the right hand side of the
_has parameter is a second _has query."

View File

@ -4,6 +4,7 @@
title: "The version of a few dependencies have been bumped to the latest versions title: "The version of a few dependencies have been bumped to the latest versions
(dependent HAPI modules listed in brackets): (dependent HAPI modules listed in brackets):
<ul> <ul>
<li>Guava (JPA): 28.2-jre -&gt; 29.0-jre</li>
</ul>" </ul>"
- item: - item:
issue: "1862" issue: "1862"

View File

@ -118,6 +118,12 @@ The following table lists vocabulary that is validated by this module:
This module validates codes using a remote FHIR-based terminology server. This module validates codes using a remote FHIR-based terminology server.
This module will invoke the following operations on the remote terminology server:
* **GET [base]/CodeSystem?url=[url]** &ndash; Tests whether a given CodeSystem is supported on the server
* **GET [base]/ValueSet?url=[url]** &ndash; Tests whether a given ValueSet is supported on the server
* **POST [base]/CodeSystem/$validate-code** &ndash; Validate codes in fields where no specific ValueSet is bound
* **POST [base]/ValueSet/$validate-code** &ndash; Validate codes in fields where a specific ValueSet is bound
# Recipes # Recipes

View File

@ -6,9 +6,9 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>5.1.0-SNAPSHOT</version> <version>5.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<artifactId>hapi-fhir-elasticsearch-6</artifactId> <artifactId>hapi-fhir-elasticsearch-6</artifactId>

View File

@ -150,6 +150,12 @@
<artifactId>hapi-fhir-elasticsearch-6</artifactId> <artifactId>hapi-fhir-elasticsearch-6</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<classifier>shaded6</classifier> <classifier>shaded6</classifier>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>

View File

@ -32,7 +32,9 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc; import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.jpa.validation.JpaFhirInstanceValidator;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.validation.IInstanceValidatorModule; import ca.uhn.fhir.validation.IInstanceValidatorModule;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
@ -88,12 +90,17 @@ public abstract class BaseConfigDstu3Plus extends BaseConfig {
@Bean(name = "myInstanceValidator") @Bean(name = "myInstanceValidator")
@Lazy @Lazy
public IInstanceValidatorModule instanceValidator() { public IInstanceValidatorModule instanceValidator() {
FhirInstanceValidator val = new FhirInstanceValidator(fhirContext()); FhirInstanceValidator val = new JpaFhirInstanceValidator(fhirContext());
val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning); val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
val.setValidationSupport(validationSupportChain()); val.setValidationSupport(validationSupportChain());
return val; return val;
} }
@Bean
public ValidationSettings validationSettings() {
return new ValidationSettings();
}
@Bean @Bean
public abstract ITermReadSvc terminologyService(); public abstract ITermReadSvc terminologyService();

View File

@ -1395,6 +1395,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional(propagation = Propagation.SUPPORTS)
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) { public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
if (theRequest != null) { if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId); ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);

View File

@ -32,7 +32,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTag;
public interface IResourceTagDao extends JpaRepository<ResourceTag, Long> { public interface IResourceTagDao extends JpaRepository<ResourceTag, Long> {
@Query("" + @Query("" +
"SELECT t FROM ResourceTag t " + "SELECT t FROM ResourceTag t " +
"INNER JOIN TagDefinition td ON (td.myId = t.myTagId) " + "INNER JOIN FETCH t.myTag td " +
"WHERE t.myResourceId in (:pids)") "WHERE t.myResourceId in (:pids)")
Collection<ResourceTag> findByResourceIds(@Param("pids") Collection<Long> pids); Collection<ResourceTag> findByResourceIds(@Param("pids") Collection<Long> pids);

View File

@ -62,6 +62,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.HasOrListParam;
import ca.uhn.fhir.rest.param.HasParam; import ca.uhn.fhir.rest.param.HasParam;
import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParameterUtil; import ca.uhn.fhir.rest.param.ParameterUtil;
@ -944,30 +945,46 @@ class PredicateBuilderReference extends BasePredicateBuilder {
throw new InvalidRequestException("Invalid resource type: " + targetResourceType); throw new InvalidRequestException("Invalid resource type: " + targetResourceType);
} }
//Ensure that the name of the search param
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
// exists on the target resource type.
RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
}
//Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val)
//exists on the target resource.
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference);
}
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>> parsedParam = (IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>>) ParameterUtil.parseQueryParams(myContext, paramDef, paramName, parameters);
ArrayList<IQueryParameterType> orValues = Lists.newArrayList(); ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
for (IQueryParameterOr<IQueryParameterType> next : parsedParam.getValuesAsQueryTokens()) { if (paramName.startsWith("_has:")) {
orValues.addAll(next.getValuesAsQueryTokens());
ourLog.trace("Handing double _has query: {}", paramName);
String qualifier = paramName.substring(4);
paramName = Constants.PARAM_HAS;
for (IQueryParameterType next : nextOrList) {
HasParam nextHasParam = new HasParam();
nextHasParam.setValueAsQueryToken(myContext, Constants.PARAM_HAS, qualifier, next.getValueAsQueryToken(myContext));
orValues.add(nextHasParam);
}
} else {
//Ensure that the name of the search param
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
// exists on the target resource type.
RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
}
//Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val)
//exists on the target resource.
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference);
}
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>> parsedParam = (IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>>) ParameterUtil.parseQueryParams(myContext, paramDef, paramName, parameters);
for (IQueryParameterOr<IQueryParameterType> next : parsedParam.getValuesAsQueryTokens()) {
orValues.addAll(next.getValuesAsQueryTokens());
}
} }
//Handle internal chain inside the has. //Handle internal chain inside the has.
if (parameterName.contains(".")) { if (parameterName.contains(".")) {
String chainedPartOfParameter = getChainedPart(parameterName); String chainedPartOfParameter = getChainedPart(parameterName);

View File

@ -105,11 +105,11 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
system = theSystem.getValue(); system = theSystem.getValue();
} }
ourLog.info("Looking up {} / {}", system, code); ourLog.debug("Looking up {} / {}", system, code);
if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) { if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) {
ourLog.info("Code system {} is supported", system); ourLog.debug("Code system {} is supported", system);
IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code); IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code);
if (retVal != null) { if (retVal != null) {
return retVal; return retVal;

View File

@ -152,7 +152,9 @@ public class TermConcept implements Serializable {
property.setType(thePropertyType); property.setType(thePropertyType);
property.setKey(thePropertyName); property.setKey(thePropertyName);
property.setValue(thePropertyValue); property.setValue(thePropertyValue);
getProperties().add(property); if (!getProperties().contains(property)) {
getProperties().add(property);
}
return property; return property;
} }
@ -412,8 +414,4 @@ public class TermConcept implements Serializable {
return getChildren().stream().map(t -> t.getChild()).collect(Collectors.toList()); return getChildren().stream().map(t -> t.getChild()).collect(Collectors.toList());
} }
public VersionIndependentConcept toVersionIndependentConcept() {
return new VersionIndependentConcept(myCodeSystem.getCodeSystem().getCodeSystemUri(), myCode);
}
} }

View File

@ -22,12 +22,25 @@ package ca.uhn.fhir.jpa.entity;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.io.Serializable; import java.io.Serializable;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -38,11 +51,9 @@ import static org.apache.commons.lang3.StringUtils.length;
@Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = { @Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = {
}) })
public class TermConceptProperty implements Serializable { public class TermConceptProperty implements Serializable {
private static final long serialVersionUID = 1L;
private static final int MAX_LENGTH = 500;
public static final int MAX_PROPTYPE_ENUM_LENGTH = 6; public static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
private static final long serialVersionUID = 1L;
private static final int MAX_LENGTH = 500;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))
private TermConcept myConcept; private TermConcept myConcept;
@ -69,14 +80,6 @@ public class TermConceptProperty implements Serializable {
private byte[] myValueLob; private byte[] myValueLob;
@Column(name = "PROP_TYPE", nullable = false, length = MAX_PROPTYPE_ENUM_LENGTH) @Column(name = "PROP_TYPE", nullable = false, length = MAX_PROPTYPE_ENUM_LENGTH)
private TermConceptPropertyTypeEnum myType; private TermConceptPropertyTypeEnum myType;
/**
* Constructor
*/
public TermConceptProperty() {
super();
}
/** /**
* Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
*/ */
@ -88,6 +91,13 @@ public class TermConceptProperty implements Serializable {
@Column(name = "PROP_DISPLAY", length = MAX_LENGTH, nullable = true) @Column(name = "PROP_DISPLAY", length = MAX_LENGTH, nullable = true)
private String myDisplay; private String myDisplay;
/**
* Constructor
*/
public TermConceptProperty() {
super();
}
/** /**
* Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
*/ */
@ -178,10 +188,6 @@ public class TermConceptProperty implements Serializable {
return myValueLob; return myValueLob;
} }
public String getValueLobAsString() {
return new String(myValueLob, StandardCharsets.UTF_8);
}
public TermConceptProperty setValueLob(byte[] theValueLob) { public TermConceptProperty setValueLob(byte[] theValueLob) {
myValueLob = theValueLob; myValueLob = theValueLob;
return this; return this;
@ -192,6 +198,10 @@ public class TermConceptProperty implements Serializable {
return this; return this;
} }
public String getValueLobAsString() {
return new String(myValueLob, StandardCharsets.UTF_8);
}
public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
myCodeSystemVersion = theCodeSystemVersion; myCodeSystemVersion = theCodeSystemVersion;
return this; return this;
@ -210,4 +220,35 @@ public class TermConceptProperty implements Serializable {
.toString(); .toString();
} }
@Override
public boolean equals(Object theO) {
if (this == theO) {
return true;
}
if (theO == null || getClass() != theO.getClass()) {
return false;
}
TermConceptProperty that = (TermConceptProperty) theO;
return new EqualsBuilder()
.append(myKey, that.myKey)
.append(myValue, that.myValue)
.append(myType, that.myType)
.append(myCodeSystem, that.myCodeSystem)
.append(myDisplay, that.myDisplay)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myKey)
.append(myValue)
.append(myType)
.append(myCodeSystem)
.append(myDisplay)
.toHashCode();
}
} }

View File

@ -408,7 +408,7 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
.build() .build()
.execute(new HttpGet(thePackageUrl))) { .execute(new HttpGet(thePackageUrl))) {
if (request.getStatusLine().getStatusCode() != 200) { if (request.getStatusLine().getStatusCode() != 200) {
throw new IOException("Received HTTP " + request.getStatusLine().getStatusCode()); throw new ResourceNotFoundException("Received HTTP " + request.getStatusLine().getStatusCode() + " from URL: " + thePackageUrl);
} }
return IOUtils.toByteArray(request.getEntity().getContent()); return IOUtils.toByteArray(request.getEntity().getContent());
} catch (IOException e) { } catch (IOException e) {

View File

@ -89,6 +89,7 @@ import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import net.bytebuddy.implementation.bytecode.Throw;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
@ -177,6 +178,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L); private static final TermCodeSystemVersion NO_CURRENT_VERSION = new TermCodeSystemVersion().setId(-1L);
private static boolean ourLastResultsFromTranslationCache; // For testing. private static boolean ourLastResultsFromTranslationCache; // For testing.
private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing. private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing.
private static Runnable myInvokeOnNextCallForUnitTest;
private final int myFetchSize = DEFAULT_FETCH_SIZE; private final int myFetchSize = DEFAULT_FETCH_SIZE;
private final Cache<String, TermCodeSystemVersion> myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(); private final Cache<String, TermCodeSystemVersion> myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
@Autowired @Autowired
@ -1299,7 +1301,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
@Nullable @Nullable
private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) { private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) {
TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> { TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> myTxTemplate.execute(tx -> {
TermCodeSystemVersion csv = null; TermCodeSystemVersion csv = null;
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri); TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri);
if (cs != null && cs.getCurrentVersion() != null) { if (cs != null && cs.getCurrentVersion() != null) {
@ -1310,7 +1312,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} else { } else {
return NO_CURRENT_VERSION; return NO_CURRENT_VERSION;
} }
}); }));
if (retVal == NO_CURRENT_VERSION) { if (retVal == NO_CURRENT_VERSION) {
return null; return null;
} }
@ -1935,8 +1937,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
@Override @Override
@Transactional
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
if (myInvokeOnNextCallForUnitTest != null) {
Runnable invokeOnNextCallForUnitTest = myInvokeOnNextCallForUnitTest;
myInvokeOnNextCallForUnitTest = null;
invokeOnNextCallForUnitTest.run();
}
IPrimitiveType<?> urlPrimitive = myContext.newTerser().getSingleValueOrNull(theValueSet, "url", IPrimitiveType.class); IPrimitiveType<?> urlPrimitive = myContext.newTerser().getSingleValueOrNull(theValueSet, "url", IPrimitiveType.class);
String url = urlPrimitive.getValueAsString(); String url = urlPrimitive.getValueAsString();
if (isNotBlank(url)) { if (isNotBlank(url)) {
@ -2106,6 +2115,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
@VisibleForTesting
public static void setInvokeOnNextCallForUnitTest(Runnable theInvokeOnNextCallForUnitTest) {
myInvokeOnNextCallForUnitTest = theInvokeOnNextCallForUnitTest;
}
static List<TermConcept> toPersistedConcepts(List<CodeSystem.ConceptDefinitionComponent> theConcept, TermCodeSystemVersion theCodeSystemVersion) { static List<TermConcept> toPersistedConcepts(List<CodeSystem.ConceptDefinitionComponent> theConcept, TermCodeSystemVersion theCodeSystemVersion) {
ArrayList<TermConcept> retVal = new ArrayList<>(); ArrayList<TermConcept> retVal = new ArrayList<>();

View File

@ -447,16 +447,19 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
doDelete(descriptor, loader, counter, myConceptDao); doDelete(descriptor, loader, counter, myConceptDao);
} }
Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
if (codeSystemOpt.isPresent()) { txTemplate.executeWithoutResult(tx -> {
TermCodeSystem codeSystem = codeSystemOpt.get(); Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid);
ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid()); if (codeSystemOpt.isPresent()) {
codeSystem.setCurrentVersion(null); TermCodeSystem codeSystem = codeSystemOpt.get();
myCodeSystemDao.save(codeSystem); ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid());
} codeSystem.setCurrentVersion(null);
myCodeSystemDao.save(codeSystem);
}
ourLog.info(" * Deleting code system version"); ourLog.info(" * Deleting code system version");
myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid); myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid);
});
} }

View File

@ -103,11 +103,6 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc {
@Override @Override
public UploadStatistics loadLoinc(List<FileDescriptor> theFiles, RequestDetails theRequestDetails) { public UploadStatistics loadLoinc(List<FileDescriptor> theFiles, RequestDetails theRequestDetails) {
try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) { try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) {
List<String> loincUploadPropertiesFragment = Collections.singletonList(
LOINC_UPLOAD_PROPERTIES_FILE.getCode()
);
descriptors.verifyMandatoryFilesExist(loincUploadPropertiesFragment);
Properties uploadProperties = getProperties(descriptors, LOINC_UPLOAD_PROPERTIES_FILE.getCode()); Properties uploadProperties = getProperties(descriptors, LOINC_UPLOAD_PROPERTIES_FILE.getCode());
List<String> mandatoryFilenameFragments = Arrays.asList( List<String> mandatoryFilenameFragments = Arrays.asList(
@ -119,7 +114,8 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc {
uploadProperties.getProperty(LOINC_IEEE_MEDICAL_DEVICE_CODE_MAPPING_TABLE_FILE.getCode(), LOINC_IEEE_MEDICAL_DEVICE_CODE_MAPPING_TABLE_FILE_DEFAULT.getCode()), uploadProperties.getProperty(LOINC_IEEE_MEDICAL_DEVICE_CODE_MAPPING_TABLE_FILE.getCode(), LOINC_IEEE_MEDICAL_DEVICE_CODE_MAPPING_TABLE_FILE_DEFAULT.getCode()),
uploadProperties.getProperty(LOINC_IMAGING_DOCUMENT_CODES_FILE.getCode(), LOINC_IMAGING_DOCUMENT_CODES_FILE_DEFAULT.getCode()), uploadProperties.getProperty(LOINC_IMAGING_DOCUMENT_CODES_FILE.getCode(), LOINC_IMAGING_DOCUMENT_CODES_FILE_DEFAULT.getCode()),
uploadProperties.getProperty(LOINC_PART_FILE.getCode(), LOINC_PART_FILE_DEFAULT.getCode()), uploadProperties.getProperty(LOINC_PART_FILE.getCode(), LOINC_PART_FILE_DEFAULT.getCode()),
uploadProperties.getProperty(LOINC_PART_LINK_FILE.getCode(), LOINC_PART_LINK_FILE_DEFAULT.getCode()), uploadProperties.getProperty(LOINC_PART_LINK_FILE_PRIMARY.getCode(), LOINC_PART_LINK_FILE_PRIMARY_DEFAULT.getCode()),
uploadProperties.getProperty(LOINC_PART_LINK_FILE_SUPPLEMENTARY.getCode(), LOINC_PART_LINK_FILE_SUPPLEMENTARY_DEFAULT.getCode()),
uploadProperties.getProperty(LOINC_PART_RELATED_CODE_MAPPING_FILE.getCode(), LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()), uploadProperties.getProperty(LOINC_PART_RELATED_CODE_MAPPING_FILE.getCode(), LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()),
uploadProperties.getProperty(LOINC_RSNA_PLAYBOOK_FILE.getCode(), LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()), uploadProperties.getProperty(LOINC_RSNA_PLAYBOOK_FILE.getCode(), LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()),
uploadProperties.getProperty(LOINC_TOP2000_COMMON_LAB_RESULTS_SI_FILE.getCode(), LOINC_TOP2000_COMMON_LAB_RESULTS_SI_FILE_DEFAULT.getCode()), uploadProperties.getProperty(LOINC_TOP2000_COMMON_LAB_RESULTS_SI_FILE.getCode(), LOINC_TOP2000_COMMON_LAB_RESULTS_SI_FILE_DEFAULT.getCode()),
@ -238,6 +234,13 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc {
@NotNull @NotNull
private Properties getProperties(LoadedFileDescriptors theDescriptors, String thePropertiesFile) { private Properties getProperties(LoadedFileDescriptors theDescriptors, String thePropertiesFile) {
Properties retVal = new Properties(); Properties retVal = new Properties();
try (InputStream propertyStream = TermLoaderSvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loincupload.properties")) {
retVal.load(propertyStream);
} catch (IOException e) {
throw new InternalErrorException("Failed to process loinc.properties", e);
}
for (FileDescriptor next : theDescriptors.getUncompressedFileDescriptors()) { for (FileDescriptor next : theDescriptors.getUncompressedFileDescriptors()) {
if (next.getFilename().endsWith(thePropertiesFile)) { if (next.getFilename().endsWith(thePropertiesFile)) {
try { try {
@ -425,10 +428,6 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc {
handler = new LoincRsnaPlaybookHandler(code2concept, valueSets, conceptMaps, theUploadProperties); handler = new LoincRsnaPlaybookHandler(code2concept, valueSets, conceptMaps, theUploadProperties);
iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_RSNA_PLAYBOOK_FILE.getCode(), LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false); iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_RSNA_PLAYBOOK_FILE.getCode(), LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false);
// Part link
handler = new LoincPartLinkHandler(codeSystemVersion, code2concept);
iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_PART_LINK_FILE.getCode(), LOINC_PART_LINK_FILE_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false);
// Part related code mapping // Part related code mapping
handler = new LoincPartRelatedCodeMappingHandler(code2concept, valueSets, conceptMaps, theUploadProperties); handler = new LoincPartRelatedCodeMappingHandler(code2concept, valueSets, conceptMaps, theUploadProperties);
iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_PART_RELATED_CODE_MAPPING_FILE.getCode(), LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false); iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_PART_RELATED_CODE_MAPPING_FILE.getCode(), LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false);
@ -469,6 +468,11 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc {
handler = new LoincParentGroupFileHandler(code2concept, valueSets, conceptMaps, theUploadProperties); handler = new LoincParentGroupFileHandler(code2concept, valueSets, conceptMaps, theUploadProperties);
iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_PARENT_GROUP_FILE.getCode(), LOINC_PARENT_GROUP_FILE_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false); iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_PARENT_GROUP_FILE.getCode(), LOINC_PARENT_GROUP_FILE_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false);
// Part link
handler = new LoincPartLinkHandler(codeSystemVersion, code2concept, propertyNamesToTypes);
iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_PART_LINK_FILE_PRIMARY.getCode(), LOINC_PART_LINK_FILE_PRIMARY_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false);
iterateOverZipFile(theDescriptors, theUploadProperties.getProperty(LOINC_PART_LINK_FILE_SUPPLEMENTARY.getCode(), LOINC_PART_LINK_FILE_SUPPLEMENTARY_DEFAULT.getCode()), handler, ',', QuoteMode.NON_NUMERIC, false);
IOUtils.closeQuietly(theDescriptors); IOUtils.closeQuietly(theDescriptors);
valueSets.add(getValueSetLoincAll()); valueSets.add(getValueSetLoincAll());

View File

@ -144,7 +144,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
if (!haveValidated) { if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
} }
if (codeOpt != null && codeOpt.isPresent()) { if (codeOpt != null && codeOpt.isPresent()) {

View File

@ -112,7 +112,7 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
if (!haveValidated) { if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
} }
if (codeOpt != null && codeOpt.isPresent()) { if (codeOpt != null && codeOpt.isPresent()) {

View File

@ -99,7 +99,7 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
if (!haveValidated) { if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
} }
if (codeOpt != null && codeOpt.isPresent()) { if (codeOpt != null && codeOpt.isPresent()) {

View File

@ -86,44 +86,9 @@ public class LoincHandler implements IRecordHandler {
concept.addPropertyString(nextPropertyName, nextPropertyValue); concept.addPropertyString(nextPropertyName, nextPropertyValue);
break; break;
case CODING: case CODING:
// TODO: handle "Ser/Plas^Donor" // These are handles by the LOINC PartLink file
String propertyValue = nextPropertyValue;
if (nextPropertyName.equals("COMPONENT")) {
if (propertyValue.contains("^")) {
propertyValue = propertyValue.substring(0, propertyValue.indexOf("^"));
} else if (propertyValue.contains("/")) {
propertyValue = propertyValue.substring(0, propertyValue.indexOf("/"));
}
}
PartTypeAndPartName key = new PartTypeAndPartName(nextPropertyName, propertyValue);
String partNumber = myPartTypeAndPartNameToPartNumber.get(key);
if (partNumber == null && nextPropertyName.equals("TIME_ASPCT")) {
key = new PartTypeAndPartName("TIME", nextPropertyValue);
partNumber = myPartTypeAndPartNameToPartNumber.get(key);
}
if (partNumber == null && nextPropertyName.equals("METHOD_TYP")) {
key = new PartTypeAndPartName("METHOD", nextPropertyValue);
partNumber = myPartTypeAndPartNameToPartNumber.get(key);
}
if (partNumber == null && nextPropertyName.equals("SCALE_TYP")) {
key = new PartTypeAndPartName("SCALE", nextPropertyValue);
partNumber = myPartTypeAndPartNameToPartNumber.get(key);
}
if (partNumber == null && nextPropertyName.equals("SYSTEM") && nextPropertyValue.startsWith("^")) {
continue;
}
if (isNotBlank(partNumber)) {
concept.addPropertyCoding(nextPropertyName, ITermLoaderSvc.LOINC_URI, partNumber, nextPropertyValue);
} else {
String msg = "Unable to find part code with TYPE[" + key.getPartType() + "] and NAME[" + nextPropertyValue + "] (using name " + propertyValue + ")";
ourLog.warn(msg);
// throw new InternalErrorException(msg);
}
break; break;
case DECIMAL: case DECIMAL:
case CODE: case CODE:
case INTEGER: case INTEGER:

View File

@ -22,12 +22,17 @@ package ca.uhn.fhir.jpa.term.loinc;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptProperty;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.csv.CSVRecord; import org.apache.commons.csv.CSVRecord;
import org.hl7.fhir.r4.model.CodeSystem;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.trim; import static org.apache.commons.lang3.StringUtils.trim;
@ -36,40 +41,65 @@ public class LoincPartLinkHandler implements IRecordHandler {
private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class); private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class);
private final Map<String, TermConcept> myCode2Concept; private final Map<String, TermConcept> myCode2Concept;
private final TermCodeSystemVersion myCodeSystemVersion; private final TermCodeSystemVersion myCodeSystemVersion;
private final Map<String, CodeSystem.PropertyType> myPropertyNames;
private Long myPartCount; private Long myPartCount;
public LoincPartLinkHandler(TermCodeSystemVersion theCodeSystemVersion, Map<String, TermConcept> theCode2concept) { public LoincPartLinkHandler(TermCodeSystemVersion theCodeSystemVersion, Map<String, TermConcept> theCode2concept, Map<String, CodeSystem.PropertyType> thePropertyNames) {
myCodeSystemVersion = theCodeSystemVersion; myCodeSystemVersion = theCodeSystemVersion;
myCode2Concept = theCode2concept; myCode2Concept = theCode2concept;
myPropertyNames = thePropertyNames;
} }
@Override @Override
public void accept(CSVRecord theRecord) { public void accept(CSVRecord theRecord) {
String loincNumber = trim(theRecord.get("LoincNumber")); String loincNumber = trim(theRecord.get("LoincNumber"));
String longCommonName = trim(theRecord.get("LongCommonName")); String property = trim(theRecord.get("Property"));
String partName = trim(theRecord.get("PartName"));
String partNumber = trim(theRecord.get("PartNumber")); String partNumber = trim(theRecord.get("PartNumber"));
/*
* Property has the form http://loinc.org/property/COMPONENT
* but we want just the COMPONENT part
*/
int lastSlashIdx = property.lastIndexOf("/");
String propertyPart = property.substring(lastSlashIdx + 1);
TermConcept loincConcept = myCode2Concept.get(loincNumber); TermConcept loincConcept = myCode2Concept.get(loincNumber);
TermConcept partConcept = myCode2Concept.get(partNumber);
if (loincConcept == null) { if (loincConcept == null) {
ourLog.warn("No loinc code: {}", loincNumber); throw new InternalErrorException("Unknown loinc code: " + loincNumber);
return;
} }
if (partConcept == null) {
if (myPartCount == null) { CodeSystem.PropertyType propertyType = myPropertyNames.get(propertyPart);
myPartCount = myCode2Concept if (propertyType == null) {
.keySet()
.stream()
.filter(t->t.startsWith("LP"))
.count();
}
ourLog.debug("No part code: {} - Have {} part codes", partNumber, myPartCount);
return; return;
} }
// For now we're ignoring these String expectedValue;
if (propertyType == CodeSystem.PropertyType.STRING) {
expectedValue = partName;
} else if (propertyType == CodeSystem.PropertyType.CODING) {
expectedValue = partNumber;
} else {
throw new InternalErrorException("Don't know how to handle property of type: " + propertyType);
}
Optional<TermConceptProperty> existingProprty = loincConcept
.getProperties()
.stream()
.filter(t -> t.getKey().equals(propertyPart))
.filter(t -> t.getValue().equals(expectedValue))
.findFirst();
if (existingProprty.isPresent()) {
return;
}
ourLog.info("Adding new property {} = {}", propertyPart, partNumber);
if (propertyType == CodeSystem.PropertyType.STRING) {
loincConcept.addPropertyString(propertyPart, partName);
} else {
loincConcept.addPropertyCoding(propertyPart, ITermLoaderSvc.LOINC_URI, partNumber, partName);
}
} }
} }

View File

@ -88,6 +88,9 @@ public class LoincPartRelatedCodeMappingHandler extends BaseLoincHandler impleme
case "wider": case "wider":
equivalence = Enumerations.ConceptMapEquivalence.WIDER; equivalence = Enumerations.ConceptMapEquivalence.WIDER;
break; break;
case "relatedto":
equivalence = Enumerations.ConceptMapEquivalence.RELATEDTO;
break;
default: default:
throw new InternalErrorException("Unknown equivalence '" + mapType + "' for PartNumber: " + partNumber); throw new InternalErrorException("Unknown equivalence '" + mapType + "' for PartNumber: " + partNumber);
} }

View File

@ -67,9 +67,13 @@ public enum LoincUploadPropertiesEnum {
// Part // Part
LOINC_PART_FILE("loinc.part.file"), LOINC_PART_FILE("loinc.part.file"),
LOINC_PART_FILE_DEFAULT("AccessoryFiles/PartFile/Part.csv"), LOINC_PART_FILE_DEFAULT("AccessoryFiles/PartFile/Part.csv"),
// Part link // Part link
LOINC_PART_LINK_FILE("loinc.part.link.file"), LOINC_PART_LINK_FILE_PRIMARY("loinc.part.link.primary.file"),
LOINC_PART_LINK_FILE_DEFAULT("AccessoryFiles/PartFile/LoincPartLink.csv"), LOINC_PART_LINK_FILE_PRIMARY_DEFAULT("AccessoryFiles/PartFile/LoincPartLink_Primary.csv"),
LOINC_PART_LINK_FILE_SUPPLEMENTARY("loinc.part.link.supplementary.file"),
LOINC_PART_LINK_FILE_SUPPLEMENTARY_DEFAULT("AccessoryFiles/PartFile/LoincPartLink_Supplementary.csv"),
// Part related code mapping // Part related code mapping
LOINC_PART_RELATED_CODE_MAPPING_FILE("loinc.part.related.code.mapping.file"), LOINC_PART_RELATED_CODE_MAPPING_FILE("loinc.part.related.code.mapping.file"),
LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT("AccessoryFiles/PartFile/PartRelatedCodeMapping.csv"), LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT("AccessoryFiles/PartFile/PartRelatedCodeMapping.csv"),

View File

@ -0,0 +1,114 @@
package ca.uhn.fhir.jpa.validation;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Locale;
public class JpaFhirInstanceValidator extends FhirInstanceValidator {
private static final Logger ourLog = LoggerFactory.getLogger(JpaFhirInstanceValidator.class);
private final FhirContext myFhirContext;
@Autowired
private ValidationSettings myValidationSettings;
@Autowired
private DaoRegistry myDaoRegistry;
/**
* Constructor
*/
public JpaFhirInstanceValidator(FhirContext theFhirContext) {
super(theFhirContext);
myFhirContext = theFhirContext;
setValidatorResourceFetcher(new MyValidatorResourceFetcher());
}
private class MyValidatorResourceFetcher implements IResourceValidator.IValidatorResourceFetcher {
@SuppressWarnings("ConstantConditions")
@Override
public Element fetch(Object appContext, String theUrl) throws FHIRException {
IdType id = new IdType(theUrl);
String resourceType = id.getResourceType();
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
IBaseResource target;
try {
target = dao.read(id, (RequestDetails) appContext);
} catch (ResourceNotFoundException e) {
ourLog.info("Failed to resolve local reference: {}", theUrl);
return null;
}
try {
return new JsonParser(provideWorkerContext()).parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType);
} catch (Exception e) {
throw new FHIRException(e);
}
}
@Override
public IResourceValidator.ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url) {
int slashIdx = url.indexOf("/");
if (slashIdx > 0 && myFhirContext.getResourceTypes().contains(url.substring(0, slashIdx))) {
return myValidationSettings.getLocalReferenceValidationDefaultPolicy();
}
return IResourceValidator.ReferenceValidationPolicy.IGNORE;
}
@Override
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
return true;
}
@Override
public byte[] fetchRaw(String url) throws IOException {
return new byte[0];
}
@Override
public void setLocale(Locale locale) {
// ignore
}
}
}

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.validation;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.thymeleaf.util.Validate;
import javax.annotation.Nonnull;
public class ValidationSettings {
private IResourceValidator.ReferenceValidationPolicy myLocalReferenceValidationDefaultPolicy = IResourceValidator.ReferenceValidationPolicy.IGNORE;
/**
* Supplies a default policy for validating local references. Default is {@literal IResourceValidator.ReferenceValidationPolicy.IGNORE}.
* <p>
* Note that this setting can have a measurable impact on validation performance, as it will cause reference targets
* to be resolved during validation. In other words, if a resource has a reference to (for example) "Patient/123", the
* resource with that ID will be loaded from the database during validation.
* </p>
*
* @since 5.1.0
*/
@Nonnull
public IResourceValidator.ReferenceValidationPolicy getLocalReferenceValidationDefaultPolicy() {
return myLocalReferenceValidationDefaultPolicy;
}
/**
* Supplies a default policy for validating local references. Default is {@literal IResourceValidator.ReferenceValidationPolicy.IGNORE}.
* <p>
* Note that this setting can have a measurable impact on validation performance, as it will cause reference targets
* to be resolved during validation. In other words, if a resource has a reference to (for example) "Patient/123", the
* resource with that ID will be loaded from the database during validation.
* </p>
*
* @since 5.1.0
*/
public void setLocalReferenceValidationDefaultPolicy(@Nonnull IResourceValidator.ReferenceValidationPolicy theLocalReferenceValidationDefaultPolicy) {
Validate.notNull(theLocalReferenceValidationDefaultPolicy, "theLocalReferenceValidationDefaultPolicy must not be null");
myLocalReferenceValidationDefaultPolicy = theLocalReferenceValidationDefaultPolicy;
}
}

View File

@ -1,442 +1,548 @@
<!-- <!--
LOINC is a well maintained, version independent code system LOINC is a freely available international standard for tests, measurements, and observations. It is a well maintained, version independent code system.
This CodeSystem resource describes 'LOINC' independent of Use of LOINC is governed by the LOINC License: https://loinc.org/license/
any particular version. There are notes about changes for
version specific LOINC code system resources.
Note that the following set of codes constitute the This CodeSystem resource describes 'LOINC' independent of any particular version. There are notes about changes for version specific LOINC code system resources.
LOINC code systems:
Note that the following set of codes are defined by the LOINC code systems:
- the main LOINC codes - the main LOINC codes
- the LOINC answer codes (LA-) and the LOINC answer list codes (LL-) - the LOINC Answer codes (LA-) and the LOINC Answer list codes (LL-)
- the Part codes in the Multiaxial Hierarchy - the LOINC Part codes (LP-) in the Multiaxial Hierarchy
- the Part codes for the properties. - the LOINC Part codes (LP-) for the properties
Note: there are license restrictions on the use of LOINC Part codes Note: there are license restrictions on the use of LOINC Part codes
- the LOINC Group codes (LG-)
Note: presently the LOINC Group codes are used to identify these roll-up groups as ValueSets, but are not yet loaded as codes in the CodeSystem
Servers may generate variants of this for the LOINC version(s) and features they support. Servers may generate variants of this for the LOINC version(s) and features they support.
File Version: 0.6
--> -->
<!--
Version History of this specification
0.1 | published 2016 11 18
0.2 | published 2017 03 10 (fixed rad properties, removed list-specific LA properties, typos)
0.3 | published 2017 05 09 (removed CHNGE_TYP based on LOINC Committee recommendation, change filter types from code vs coding -> which allows use of the LP codes or the Part names)
0.4 | published 2018 02 09 (fixed multiaxial hierarchy relationship, added clarifying statement about English as the language for filters)
-->
<CodeSystem xmlns="http://hl7.org/fhir"> <CodeSystem xmlns="http://hl7.org/fhir">
<id value="loinc"/> <id value="loinc"/>
<!-- This url is unchanged for all versions of LOINC. There <!--
can only be one correct Code System resource for each value of the This url is unchanged for all versions of LOINC. There can only be one correct Code System resource for each value of the version attribute (at least, only one per server).
version attribute (at least, only one per server) --> -->
<url value="http://loinc.org"/> <url value="http://loinc.org"/>
<!-- the HL7 v3 OID assigned to LOINC --> <!-- the HL7 v3 OID assigned to LOINC -->
<identifier> <identifier>
<system value="urn:ietf:rfc:3986"/> <system value="urn:ietf:rfc:3986"/>
<value value="urn:oid:2.16.840.1.113883.6.1"/> <value value="urn:oid:2.16.840.1.113883.6.1"/>
</identifier> </identifier>
<!-- <!--
// if a version is specified: If a version is specified:
<version value="2.59"/> <version value="2.68"/>
-->
<!--
If a specific version is specified, the name should carry this information (e.g. LOINC_268).
-->
<name value="LOINC"/>
<title value="LOINC Code System"/>
<status value="active"/>
<experimental value="false"/>
<publisher value="Regenstrief Institute, Inc."/>
<contact>
<telecom>
<value value="http://loinc.org"/>
</telecom>
</contact>
<!--
<date value=2020-06/>
-->
<description value="LOINC is a freely available international standard for tests, measurements, and observations"/>
<copyright value="This material contains content from LOINC (http://loinc.org). LOINC is copyright ©1995-2020, Regenstrief Institute, Inc. and the Logical Observation Identifiers Names and Codes (LOINC) Committee and is available at no cost under the license at http://loinc.org/license. LOINC® is a registered United States trademark of Regenstrief Institute, Inc."/>
<caseSensitive value="false"/>
<valueSet value=" http://loinc.org/vs"/>
<!--
For a version specific reference:
<valueSet value="http://loinc.org/2.68/vs"/>
--> -->
<!-- if a specific version is specified, the name should carry this information should be in the name (e.g. LOINC_259) and title --> <!--
<name value="LOINC"/> It's at the discretion of servers whether to present fragments of LOINC hierarchically or not, when using the code system resource. But, if they are hierarchical, the Hierarchy SHALL be based on the is-a relationship that is derived from the LOINC Multiaxial Hierarchy.
<title value="LOINC Code System"/> -->
<status value="active"/> <hierarchyMeaning value="is-a"/>
<experimental value="false"/> <compositional value="false"/> <!-- no compositional grammar in LOINC -->
<versionNeeded value="false"/>
<publisher value="Regenstrief Institute, Inc."/>
<contact>
<telecom>
<value value="http://loinc.org"/>
</telecom>
</contact>
<!--
<date value=[date for this version]"/>
-->
<description value="LOINC is a freely available international standard for tests, measurements, and observations"/>
<copyright value="This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at http://loinc.org/terms-of-use"/>
<caseSensitive value="false"/>
<valueSet value=" http://loinc.org/vs"/>
<!--
for a version specific reference:
<valueSet value="http://loinc.org/2.56/vs"/>
-->
<!--
It's at the discretion of servers whether to present fragments of LOINC heirarchically or not, when
using the code system resource. But, if they are heirarchical, the Hierarchy SHALL be based on the is-a relationship that is derived from the LOINC Multiaxial Hierarchy.
-->
<HierarchyMeaning value="is-a"/>
<compositional value="false"/> <!-- no compositional grammar in LOINC -->
<versionNeeded value="false"/>
<!-- this canonical definition of LOINC does not include the content.
Servers may choose to include fragments (but not, due to size constraints, all of LOINC) -->
<content value="not-present"/>
<!-- <count value="65000"/>... if working with a specific version, you could nominate a count of the total number of concepts (including the answers, Hierarchy, etc.) -->
<!-- <!--
Generally defined filters for specifying value sets This canonical definition of LOINC does not include the LOINC content, which is distributed separately for portability.
In LOINC, all the properties can be used as filters too, but they are not defined explicitly as filters as well.
Note that parent/child/ancestor/descendant are defined by FHIR, but repeated here to document them clearly.
For illustration purposes, consider this slice of the LOINC Multiaxial Hierarchy when reading the descriptions:
Microbiology [LP31755-9]
Microorganism [LP14559-6]
Virus [LP14855-8]
Zika virus [LP200137-0]
Zika virus RNA | XXX [LP203413-2]
Zika virus RNA [Presence] in Unspecified specimen by Probe and target amplification method [79190-5]
Language Note: The filters defined here are specified using the default LOINC language - English (US). Requests are meant to be specified and interpreted on the English version. The return can be in a specified language (if supported by the server). But note that not all filters/properties have language translations available. Servers may choose to include fragments of LOINC for illustration purposes.
--> -->
<filter> <content value="not-present"/>
<code value="parent"/>
<description value="Allows for the selection of a set of codes based on their appearance in the LOINC Multiaxial Hierarchy. parent selects immediate children only. For example, the code '79190-5' has the parent 'LP203413-2'"/>
<operator value="="/>
<value value="A Part code"/>
</filter>
<filter>
<code value="child"/>
<description value="Allows for the selection of a set of codes based on their appearance in the LOINC Multiaxial Hierarchy. child selects immediate children only. For example, the code 'LP203413-2' has the child '79190-5'"/>
<operator value="in"/>
<value value="A comma separated list of Part codes"/>
</filter>
<filter>
<code value="ancestor"/>
<description value="Allows for the selection of a set of codes based on their appearance in the LOINC Multiaxial Hierarchy. ancestor includes parents transitively, e.g. 'LP203413-2' eventually has an ancestor 'LP14559-6', so the code '79190-5' is in the set of codes that have ancestor=LP14559-6"/>
<operator value="="/>
<value value="A Part code"/>
</filter>
<filter>
<code value="descendant"/>
<description value="Allows for the selection of a set of codes based on their appearance in the LOINC Multiaxial Hierarchy. descendant includes children transitively, e.g. 'LP14559-6' eventually has a descendant 'LP203413-2', so the code '79190-5' is in the set of codes that have descendant=LP14559-6"/>
<operator value="in"/>
<value value="A comma separated list of Part codes"/>
</filter>
<filter>
<code value="copyright"/>
<description value="Allows for the inclusion or exclusion of LOINC codes that include 3rd party copyright notices. LOINC = only codes with a sole copyright by Regenstrief. 3rdParty = only codes with a 3rd party copyright in addition to the one from Regenstrief"/>
<operator value="="/>
<value value="LOINC | 3rdParty"/>
</filter>
<!-- properties. There are 3 kinds of properties:
fhir: display, designation; these are not described here since they are inherent in the specification
infrastructural: defined by FHIR, but documented here for LOINC
LOINC properties: defined by the main LOINC table
concept model: defined by the LOINC Multiaxial Hierarchy
-->
<!-- first, the infrastructural properties - inherited from FHIR, but documented here -->
<property>
<code value="parent"/>
<uri value="http://hl7.org/fhir/concept-properties#parent"/>
<description value="A parent code in the Multiaxial Hierarchy"/>
<type value=""/>
</property>
<property>
<code value="child"/>
<uri value="http://hl7.org/fhir/concept-properties#child"/>
<description value="A child code in the Multiaxial Hierarchy"/>
<type value=""/>
</property>
<!--
LOINC properties.
These apply to the main LOINC codes, but not the Multiaxial Hierarchy, the answer lists, or the part codes.
Notes:
SHORTNAME = display & LONG_COMMON_NAME = definition
Properties are specified as type "code", which are LOINC Part codes (LP-).
It is anticipated that the LOINC Part codes to be used in these properties will be published in the June 2017 LOINC release.
-->
<property>
<code value="STATUS"/>
<uri value="http://loinc.org/property/STATUS"/>
<description value="Status of the term. Within LOINC, codes with STATUS=DEPRECATED are considered inactive. Current values: ACTIVE, TRIAL, DISCOURAGED, and DEPRECATED"/>
<!-- DV NOTE: changed this from boolean to string -->
<type value="string"/>
</property>
<property>
<code value="COMPONENT"/>
<uri value="http://loinc.org/property/COMPONENT"/>
<description value="First major axis-component or analyte: Analyte Name, Analyte sub-class, Challenge"/>
<type value="Coding"/>
</property>
<property>
<code value="PROPERTY"/>
<uri value="http://loinc.org/property/PROPERTY"/>
<description value="Second major axis-property observed: Kind of Property (also called kind of quantity)"/>
<type value="Coding"/>
</property>
<property>
<code value="TIME_ASPCT"/>
<uri value="http://loinc.org/property/TIME_ASPCT"/>
<description value="Third major axis-timing of the measurement: Time Aspect (Point or moment in time vs. time interval)"/>
<type value="Coding"/>
</property>
<property>
<code value="SYSTEM"/>
<uri value="http://loinc.org/property/SYSTEM"/>
<description value="Fourth major axis-type of specimen or system: System (Sample) Type"/>
<type value="Coding"/>
</property>
<property>
<code value="SCALE_TYP"/>
<uri value="http://loinc.org/property/SCALE_TYP"/>
<description value="Fifth major axis-scale of measurement: Type of Scale"/>
<type value="Coding"/>
</property>
<property>
<code value="METHOD_TYP"/>
<uri value="http://loinc.org/property/METHOD_TYP"/>
<description value="Sixth major axis-method of measurement: Type of Method"/>
<type value="Coding"/>
</property>
<property>
<code value="CLASS"/>
<uri value="http://loinc.org/property/CLASS"/>
<description value="An arbitrary classification of the terms for grouping related observations together"/>
<type value="string"/>
</property>
<!-- Note: removed in 0.3
<property>
<code value="CHNG_TYPE"/>
<uri value="http://loinc.org/property/CHNG_TYPE"/>
<description value="A classification of the type of change made to a LOINC term, e.g. DEL=deprecated, ADD=add"/>
<type value="string"/>
</property>
-->
<property>
<code value="VersionLastChanged"/>
<uri value="http://loinc.org/property/VersionLastChanged"/>
<description value="The LOINC version number in which the record has last changed. For new records, this field contains the same value as the FirstPublishedRelease property."/>
<type value="string"/>
</property>
<property>
<code value="CONSUMER_NAME"/>
<uri value="http://loinc.org/property/CONSUMER_NAME"/>
<description value="An experimental (beta) consumer friendly name for this item. The intent is to provide a test name that health care consumers will recognize; it will be similar to the names that might appear on a lab report"/>
<type value="string"/>
</property>
<property>
<code value="CLASSTYPE"/>
<uri value="http://loinc.org/property/CLASSTYPE"/>
<description value="1=Laboratory class; 2=Clinical class; 3=Claims attachments; 4=Surveys"/>
<type value="string"/>
</property>
<property>
<code value="ORDER_OBS"/>
<uri value="http://loinc.org/property/ORDER_OBS"/>
<description value="Provides users with an idea of the intended use of the term by categorizing it as an order only, observation only, or both"/>
<type value="string"/>
</property>
<property>
<code value="HL7_ATTACHMENT_STRUCTURE"/>
<uri value="http://loinc.org/property/HL7_ATTACHMENT_STRUCTURE"/>
<description value="This property is populated in collaboration with the HL7 Attachments Work Group as described in the HL7 Attachment Specification: Supplement to Consolidated CDA Templated Guide."/>
<type value="string"/>
</property>
<property>
<code value="VersionFirstReleased"/>
<uri value="http://loinc.org/property/VersionFirstReleased"/>
<description value="This is the LOINC version number in which this LOINC term was first published."/>
<type value="string"/>
</property>
<property>
<code value="PanelType"/>
<uri value="http://loinc.org/property/PanelType"/>
<description value="For LOINC terms that are panels, this attribute classifies them as a 'Convenience group', 'Organizer', or 'Panel'"/>
<type value="string"/>
</property>
<property>
<code value="ValidHL7AttachmentRequest"/>
<uri value="http://loinc.org/property/ValidHL7AttachmentRequest"/>
<description value="A value of Y in this field indicates that this LOINC code can be sent by a payer as part of an HL7 Attachment request for additional information."/>
<type value="string"/>
</property>
<!-- LOINC/RSNA Radiology Playbook properties. These apply only to terms in the LOINC/RSNA Radiology Playbook File. <!--
Notes: <count value="65000"/>
Properties are specified as type "code", which are LOINC Part codes (LP-) If working with a specific version, you could nominate a count of the total number of concepts (including the answers, Hierarchy, etc.). In this canonical definition we do not.
Converted the attribute names from LOINC style to FHIR style b/c they contained periods -->
Maneuver sub-attributes are being released in 2016 12.
--> <!--
<property> FILTERS
<code value="rad-modality-modality-type"/> Generally defined filters for specifying value sets
<uri value="http://loinc.org/property/rad-modality-type"/> In LOINC, all the properties can also be used as filters, but they are not defined explicitly as filters.
<description value="Modality is used to represent the device used to acquire imaging information."/> Parent/child properties are as defined by FHIR. Note that at this time the LOINC code system resource does not support ancestor/descendant relationships.
<type value="Coding"/>
</property> For illustration purposes, consider this slice of the LOINC Multiaxial Hierarchy when reading the descriptions below:
<property>
<code value="rad-modality-modality-subtype"/> Microbiology [LP7819-8]
<uri value="http://loinc.org/property/rad-modality-subtype"/> Microorganism [LP14559-6]
<description value="Modality subtype may be optionally included to signify a particularly common or evocative configuration of the modality."/> Virus [LP14855-8]
<type value="Coding"/> Zika virus [LP200137-0]
</property> Zika virus RNA | XXX [LP203271-4]
<property> Zika virus RNA | XXX | Microbiology [LP379670-5]
<code value="rad-anatomic-location-region-imaged"/> Zika virus RNA [Presence] in Unspecified specimen by Probe and target amplification method [79190-5]
<uri value="http://loinc.org/property/rad-anatomic-location-region-imaged"/>
<description value="The Anatomic Location Region Imaged attribute is used in two ways: as a coarse-grained descriptor of the area imaged and a grouper for finding related imaging exams; or, it is used just as a grouper."/> Language Note: The filters defined here are specified using the default LOINC language - English (US). Requests are meant to be specified and interpreted on the English version. The return can be in a specified language (if supported by the server). But note that not all filters/properties have language translations available.
<type value="Coding"/> -->
</property> <filter>
<property> <code value="parent"/>
<code value="rad-anatomic-location-imaging-focus"/> <description value="Allows for the selection of a set of codes based on their appearance in the LOINC Multiaxial Hierarchy. Parent selects immediate parent only. For example, the code '79190-5' has the parent 'LP379670-5'"/>
<uri value="http://loinc.org/property/rad-anatomic-location-imaging-focus"/> <operator value="="/>
<description value="The Anatomic Location Imaging Focus is a more fine-grained descriptor of the specific target structure of an imaging exam. In many areas, the focus should be a specific organ."/> <value value="A Part code"/>
<type value="Coding"/> </filter>
</property> <filter>
<property> <code value="child"/>
<code value="rad-anatomic-location-laterality-presence"/> <description value="Allows for the selection of a set of codes based on their appearance in the LOINC Multiaxial Hierarchy. Child selects immediate children only. For example, the code 'LP379670-5' has the child '79190-5'. Only LOINC Parts have children; LOINC codes do not have any children because they are leaf nodes."/>
<uri value="http://loinc.org/property/rad-anatomic-location-laterality-presence"/> <operator value="="/>
<description value="Radiology Exams that require laterality to be specified in order to be performed are signified with an Anatomic Location Laterality Presence attribute set to 'True'"/> <value value="A comma separated list of Part or LOINC codes"/>
<type value="Coding"/> </filter>
</property> <filter>
<property> <code value="copyright"/>
<code value="rad-anatomic-location-laterality"/> <description value="Allows for the inclusion or exclusion of LOINC codes that include 3rd party copyright notices. LOINC = only codes with a sole copyright by Regenstrief. 3rdParty = only codes with a 3rd party copyright in addition to the one from Regenstrief"/>
<uri value="http://loinc.org/property/rad-anatomic-location-laterality"/> <operator value="="/>
<description value="Radiology exam Laterality is specified as one of: Left, Right, Bilateral, Unilateral, Unspecified"/> <value value="LOINC | 3rdParty"/>
<type value="Coding"/> </filter>
</property>
<property> <!--
<code value="rad-view-view-aggregation"/> PROPERTIES
<uri value="http://loinc.org/property/rad-view-aggregation"/> There are 4 kinds of properties that apply to all LOINC codes:
<description value="Aggregation describes the extent of the imaging performed, whether in quantitative terms (e.g., '3 or more views') or subjective terms (e.g., 'complete')."/> 1. FHIR: display, designation; these are not described here since they are inherent in the specification
<type value="Coding"/> 2. Infrastructural: defined by FHIR, but documented here for the LOINC Multiaxial Hierarchy
</property> 3. Primary LOINC properties: defined by the main LOINC table
<property> 4. Secondary LOINC properties: defined by the LoincPartLink table
<code value="rad-view-view-type"/> Additionally, there are 2 kinds of properties specific to Document ontology and Radiology codes, respectively:
<uri value="http://loinc.org/property/rad-view-view-type"/> 1. LOINC/RSNA Radiology Playbook properties
<description value="View type names specific views, such as 'lateral' or 'AP'."/> 2. Document Ontology properties
<type value="Coding"/> -->
</property> <!--
<property> Infrastructural properties - inherited from FHIR, but documented here for the LOINC Multiaxial Hierarchy.
<code value="rad-maneuver-maneuver-type"/> -->
<uri value="http://loinc.org/property/rad-maneuver-maneuver-type"/>
<description value="Maneuver type indicates an action taken with the goal of elucidating or testing a dynamic aspect of the anatomy."/> <property>
<type value="Coding"/> <code value="parent"/>
</property> <uri value="http://hl7.org/fhir/concept-properties#parent"/>
<property> <description value="A parent code in the Multiaxial Hierarchy"/>
<code value="rad-timing"/> <type value=""/>
<uri value="http://loinc.org/property/rad-timing"/> </property>
<description value="The Timing/Existence property used in conjunction with pharmaceutical and manueuver properties. It specifies whether or not the imaging occurs in the presence of the administered pharmaceutical or a manuever designed to test some dynamic aspect of anatomy or physiology ."/> <property>
<type value="Coding"/> <code value="child"/>
</property> <uri value="http://hl7.org/fhir/concept-properties#child"/>
<property> <description value="A child code in the Multiaxial Hierarchy"/>
<code value="rad-pharmaceutical-substance-given"/> <type value=""/>
<uri value="http://loinc.org/property/rad-pharmaceutical-substance-given"/> </property>
<description value="The Pharmaceutical Substance Given specifies administered contrast agents, radiopharmaceuticals, medications, or other clinically important agents and challenges during the imaging procedure."/>
<type value="Coding"/> <!--
</property> Primary LOINC properties.
<property> These apply to the main LOINC codes, but not the Multiaxial Hierarchy, Answer lists, or the Part codes.
<code value="rad-pharmaceutical-route"/> Notes:
<uri value="http://loinc.org/property/rad-pharmaceutical-route"/> In the LOINC code system resource, the display element = LONG_COMMON_NAME
<description value="Route specifies the route of administration of the pharmeceutical."/> Many properties are specified as type "coding", which allows use of LOINC Part codes (LP-) and the display text. LOINC Parts and their associations to LOINC terms are published in the LOINC Part File.
<type value="Coding"/> The properties defined here follow the guidance of the LOINC Users' Manual, which states that they should be expressed with the LOINC attributes contained in the LOINC Table. Properties that are not defined in the LOINC Table use FHIR-styled names.
</property> -->
<property>
<code value="rad-reason-for-exam"/> <property>
<uri value="http://loinc.org/property/rad-reason-for-exam"/> <code value="STATUS"/>
<description value="Reason for exam is used to describe a clinical indication or a purpose for the study."/> <uri value="http://loinc.org/property/STATUS"/>
<type value="Coding"/> <description value="Status of the term. Within LOINC, codes with STATUS=DEPRECATED are considered inactive. Current values: ACTIVE, TRIAL, DISCOURAGED, and DEPRECATED"/>
</property> <type value="string"/>
<property> </property>
<code value="rad-guidance-for-presence"/> <property>
<uri value="http://loinc.org/property/rad-guidance-for-presence"/> <code value="COMPONENT"/>
<description value="Guidance for.Presence indicates when a procedure is guided by imaging."/> <uri value="http://loinc.org/property/COMPONENT"/>
<type value="Coding"/> <description value="First major axis-component or analyte: Analyte Name, Analyte sub-class, Challenge"/>
</property> <type value="Coding"/>
<property> </property>
<code value="rad-guidance-for-approach"/> <property>
<uri value="http://loinc.org/property/rad-guidance-for-approach"/> <code value="PROPERTY"/>
<description value="Guidance for.Approach refers to the primary route of access used, such as percutaneous, transcatheter, or transhepatic."/> <uri value="http://loinc.org/property/PROPERTY"/>
<type value="Coding"/> <description value="Second major axis-property observed: Kind of Property (also called kind of quantity)"/>
</property> <type value="Coding"/>
<property> </property>
<code value="rad-guidance-for-action"/> <property>
<uri value="http://loinc.org/property/rad-guidance-for-action"/> <code value="TIME_ASPCT"/>
<description value="Guidance for.Action indicates the intervention performed, such as biopsy, aspiration, or ablation."/> <uri value="http://loinc.org/property/TIME_ASPCT"/>
<type value="Coding"/> <description value="Third major axis-timing of the measurement: Time Aspect (Point or moment in time vs. time interval)"/>
</property> <type value="Coding"/>
<property> </property>
<code value="rad-guidance-for-object"/> <property>
<uri value="http://loinc.org/property/rad-guidance-for-object"/> <code value="SYSTEM"/>
<description value="Guidance for.Object specifies the target of the action, such as mass, abscess or cyst."/> <uri value="http://loinc.org/property/SYSTEM"/>
<type value="Coding"/> <description value="Fourth major axis-type of specimen or system: System (Sample) Type"/>
</property> <type value="Coding"/>
<property> </property>
<code value="rad-subject"/> <property>
<uri value="http://loinc.org/property/rad-subject"/> <code value="SCALE_TYP"/>
<description value="Subject is intended for use when there is a need to distinguish between the patient associated with an imaging study, and the target of the study."/> <uri value="http://loinc.org/property/SCALE_TYP"/>
<type value="Coding"/> <description value="Fifth major axis-scale of measurement: Type of Scale"/>
</property> <type value="Coding"/>
<!-- Document Ontology properties. These apply only to terms in the LOINC Document Ontology File </property>
Notes: <property>
Properties are specified as type "code", which are LOINC Part codes (LP-) <code value="METHOD_TYP"/>
Converted the attribute names from LOINC style to FHIR style b/c they contained periods <uri value="http://loinc.org/property/METHOD_TYP"/>
--> <description value="Sixth major axis-method of measurement: Type of Method"/>
<property> <type value="Coding"/>
<code value="document-kind"/> </property>
<uri value="http://loinc.org/property/document-kind"/> <property>
<description value="Characterizes the general structure of the document at a macro level."/> <code value="CLASS"/>
<type value="Coding"/> <uri value="http://loinc.org/property/CLASS"/>
</property> <description value="An arbitrary classification of the terms for grouping related observations together"/>
<property> <type value="Coding"/>
<code value="document-role"/> </property>
<uri value="http://loinc.org/property/document-role"/> <property>
<description value="Characterizes the training or professional level of the author of the document, but does not break down to specialty or subspecialty.."/> <code value="VersionLastChanged"/>
<type value="Coding"/> <uri value="http://loinc.org/property/VersionLastChanged"/>
</property> <description value="The LOINC version number in which the record has last changed. For new records, this field contains the same value as the VersionFirstReleased property."/>
<property> <type value="string"/>
<code value="document-setting"/> </property>
<uri value="http://loinc.org/property/document-setting"/> <property>
<description value="Setting is a modest extension of CMSs coarse definition of care settings, such as outpatient, hospital, etc. Setting is not equivalent to location, which typically has more locally defined meanings."/> <code value="CLASSTYPE"/>
<type value="Coding"/> <uri value="http://loinc.org/property/CLASSTYPE"/>
</property> <description value="1=Laboratory class; 2=Clinical class; 3=Claims attachments; 4=Surveys"/>
<property> <type value="string"/>
<code value="document-subject-matter-domain"/> </property>
<uri value="http://loinc.org/property/document-subject-matter-domain"/> <property>
<description value="Characterizes the clinical domain that is the subject of the document. For example, Internal Medicine, Neurology, Physical Therapy, etc."/> <code value="ORDER_OBS"/>
<type value="Coding"/> <uri value="http://loinc.org/property/ORDER_OBS"/>
</property> <description value="Provides users with an idea of the intended use of the term by categorizing it as an order only, observation only, or both"/>
<property> <type value="string"/>
<code value="document-type-of-service"/> </property>
<uri value="http://loinc.org/property/document-type-of-service"/> <property>
<description value="Characterizes the kind of service or activity provided to/for the patient (or other subject of the service) that is described in the document."/> <code value="HL7_ATTACHMENT_STRUCTURE"/>
<type value="Coding"/> <uri value="http://loinc.org/property/HL7_ATTACHMENT_STRUCTURE"/>
</property> <description value="This property is populated in collaboration with the HL7 Attachments Work Group as described in the HL7 Attachment Specification: Supplement to Consolidated CDA Templated Guide."/>
<!-- Answer list related properties --> <type value="string"/>
<property> </property>
<code value="answer-list"/> <property>
<uri value="http://loinc.org/property/answer-list"/> <code value="VersionFirstReleased"/>
<description value="An answer list associated with this LOINC code (if there are matching answer lists defined). Only on normal LOINC Codes"/> <uri value="http://loinc.org/property/VersionFirstReleased"/>
<type value="Coding"/> <description value="This is the LOINC version number in which this LOINC term was first published."/>
</property> <type value="string"/>
<!-- Note: We expect to add an AnswerListType property when LOINC publishes new answer list file format in June 2017 --> </property>
<property> <property>
<code value="answers-for"/> <code value="PanelType"/>
<uri value="http://loinc.org/property/answers-for"/> <uri value="http://loinc.org/property/PanelType"/>
<description value="A LOINC Code for which this answer list is used. Only on normal LL- Codes"/> <description value="For LOINC terms that are panels, this attribute classifies them as a 'Convenience group', 'Organizer', or 'Panel'"/>
<type value="Coding"/> <type value="string"/>
</property> </property>
<!-- Note for future consideration. These are properties of LA codes in the context of a particular list. Not global properties <property>
<property> <code value="ValidHL7AttachmentRequest"/>
<code value="sequence"/> <uri value="http://loinc.org/property/ValidHL7AttachmentRequest"/>
<uri value="http://loinc.org/property/sequence"/> <description value="A value of Y in this field indicates that this LOINC code can be sent by a payer as part of an HL7 Attachment request for additional information."/>
<description value="Sequence Number of a answer in a set of answers (LA- codes only)"/> <type value="string"/>
<type value="integer"/> </property>
</property> <property>
<property> <code value="DisplayName"/>
<code value="score"/> <uri value="http://loinc.org/property/DisplayName"/>
<uri value="http://loinc.org/property/score"/> <description value="A name that is more 'clinician-friendly' compared to the current LOINC Short Name, Long Common Name, and Fully Specified Name. It is created algorithmically from the manually crafted display text for each Part and is generally more concise than the Long Common Name."/>
<description value="Score assigned to an answer (LA- codes only)"/> <type value="string"/>
<type value="integer"/> </property>
</property> <property>
--> <code value="answer-list"/>
<uri value="http://loinc.org/property/answer-list"/>
<description value="An answer list associated with this LOINC code (if there are matching answer lists defined)."/>
<type value="Coding"/>
</property>
<!--
Secondary LOINC properties.
These properties also apply to the main LOINC codes, but not the Multiaxial Hierarchy, Answer lists, or the Part codes.
Notes:
These properties are defined in the LoincPartLink table.
-->
<property>
<code value="analyte"/>
<uri value="http://loinc.org/property/analyte"/>
<description value="First sub-part of the Component, i.e., the part of the Component before the first carat"/>
<type value="Coding"/>
</property>
<property>
<code value="analyte-core"/>
<uri value="http://loinc.org/property/analyte-core"/>
<description value="The primary part of the analyte without the suffix"/>
<type value="Coding"/>
</property>
<property>
<code value="analyte-suffix"/>
<uri value="http://loinc.org/property/analyte-suffix"/>
<description value="The suffix part of the analyte, if present, e.g., Ab or DNA"/>
<type value="Coding"/>
</property>
<property>
<code value="analyte-numerator"/>
<uri value="http://loinc.org/property/analyte-numerator"/>
<description value="The numerator part of the analyte, i.e., everything before the slash in analytes that contain a divisor"/>
<type value="Coding"/>
</property>
<property>
<code value="analyte-divisor"/>
<uri value="http://loinc.org/property/analyte-divisor"/>
<description value="The divisor part of the analyte, if present, i.e., after the slash and before the first carat"/>
<type value="Coding"/>
</property>
<property>
<code value="analyte-divisor-suffix"/>
<uri value="http://loinc.org/property/analyte-divisor-suffix"/>
<description value="The suffix part of the divisor, if present"/>
<type value="Coding"/>
</property>
<property>
<code value="challenge"/>
<uri value="http://loinc.org/property/challenge"/>
<description value="Second sub-part of the Component, i.e., after the first carat"/>
<type value="Coding"/>
</property>
<property>
<code value="adjustment"/>
<uri value="http://loinc.org/property/adjustment"/>
<description value="Third sub-part of the Component, i.e., after the second carat"/>
<type value="Coding"/>
</property>
<property>
<code value="count"/>
<uri value="http://loinc.org/property/count"/>
<description value="Fourth sub-part of the Component, i.e., after the third carat"/>
<type value="Coding"/>
</property>
<property>
<code value="time-core"/>
<uri value="http://loinc.org/property/time-core"/>
<description value="The primary part of the Time"/>
<type value="Coding"/>
</property>
<property>
<code value="time-modifier"/>
<uri value="http://loinc.org/property/time-modifier"/>
<description value="The modifier of the Time value, such as mean or max"/>
<type value="Coding"/>
</property>
<property>
<code value="system-core"/>
<uri value="http://loinc.org/property/system-core"/>
<description value="The primary part of the System, i.e., without the super system"/>
<type value="Coding"/>
</property>
<property>
<code value="super-system"/>
<uri value="http://loinc.org/property/super-system"/>
<description value="The super system part of the System, if present. The super system represents the source of the specimen when the source is someone or something other than the patient whose chart the result will be stored in. For example, fetus is the super system for measurements done on obstetric ultrasounds, because the fetus is being measured and that measurement is being recorded in the patient's (mother's) chart."/>
<type value="Coding"/>
</property>
<property>
<code value="analyte-gene"/>
<uri value="http://loinc.org/property/analyte-gene"/>
<description value="The specific gene represented in the analyte"/>
<type value="Coding"/>
</property>
<property>
<code value="category"/>
<uri value="http://loinc.org/property/category"/>
<description value="A single LOINC term can be assigned one or more categories based on both programmatic and manual tagging. Category properties also utilize LOINC Class Parts."/>
<type value="Coding"/>
</property>
<property>
<code value="search"/>
<uri value="http://loinc.org/property/search"/>
<description value="Synonyms, fragments, and other Parts that are linked to a term to enable more encompassing search results."/>
<type value="Coding"/>
</property>
<!--
LOINC/RSNA Radiology Playbook properties. These apply only to terms in the LOINC/RSNA Radiology Playbook File.
Notes:
Properties are specified as type "coding", which are represented by LOINC Part codes (LP-) and their display names.
The attribute names here use FHIR styled names rather than their original LOINC style names because the original names contain periods.
-->
<property>
<code value="rad-modality-modality-type"/>
<uri value="http://loinc.org/property/rad-modality-modality-type"/>
<description value="Modality is used to represent the device used to acquire imaging information."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-modality-modality-subtype"/>
<uri value="http://loinc.org/property/rad-modality-modality-subtype"/>
<description value="Modality subtype may be optionally included to signify a particularly common or evocative configuration of the modality."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-anatomic-location-region-imaged"/>
<uri value="http://loinc.org/property/rad-anatomic-location-region-imaged"/>
<description value="The Anatomic Location Region Imaged attribute is used in two ways: as a coarse-grained descriptor of the area imaged and a grouper for finding related imaging exams; or, it is used just as a grouper."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-anatomic-location-imaging-focus"/>
<uri value="http://loinc.org/property/rad-anatomic-location-imaging-focus"/>
<description value="The Anatomic Location Imaging Focus is a more fine-grained descriptor of the specific target structure of an imaging exam. In many areas, the focus should be a specific organ."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-anatomic-location-laterality-presence"/>
<uri value="http://loinc.org/property/rad-anatomic-location-laterality-presence"/>
<description value="Radiology Exams that require laterality to be specified in order to be performed are signified with an Anatomic Location Laterality Presence attribute set to 'True'"/>
<type value="Coding"/>
</property>
<property>
<code value="rad-anatomic-location-laterality"/>
<uri value="http://loinc.org/property/rad-anatomic-location-laterality"/>
<description value="Radiology exam Laterality is specified as one of: Left, Right, Bilateral, Unilateral, Unspecified"/>
<type value="Coding"/>
</property>
<property>
<code value="rad-view-aggregation"/>
<uri value="http://loinc.org/property/rad-view-aggregation"/>
<description value="Aggregation describes the extent of the imaging performed, whether in quantitative terms (e.g., '3 or more views') or subjective terms (e.g., 'complete')."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-view-view-type"/>
<uri value="http://loinc.org/property/rad-view-view-type"/>
<description value="View type names specific views, such as 'lateral' or 'AP'."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-maneuver-maneuver-type"/>
<uri value="http://loinc.org/property/rad-maneuver-maneuver-type"/>
<description value="Maneuver type indicates an action taken with the goal of elucidating or testing a dynamic aspect of the anatomy."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-timing"/>
<uri value="http://loinc.org/property/rad-timing"/>
<description value="The Timing/Existence property used in conjunction with pharmaceutical and maneuver properties. It specifies whether or not the imaging occurs in the presence of the administered pharmaceutical or a maneuver designed to test some dynamic aspect of anatomy or physiology ."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-pharmaceutical-substance-given"/>
<uri value="http://loinc.org/property/rad-pharmaceutical-substance-given"/>
<description value="The Pharmaceutical Substance Given specifies administered contrast agents, radiopharmaceuticals, medications, or other clinically important agents and challenges during the imaging procedure."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-pharmaceutical-route"/>
<uri value="http://loinc.org/property/rad-pharmaceutical-route"/>
<description value="Route specifies the route of administration of the pharmaceutical."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-reason-for-exam"/>
<uri value="http://loinc.org/property/rad-reason-for-exam"/>
<description value="Reason for exam is used to describe a clinical indication or a purpose for the study."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-guidance-for-presence"/>
<uri value="http://loinc.org/property/rad-guidance-for-presence"/>
<description value="Guidance for.Presence indicates when a procedure is guided by imaging."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-guidance-for-approach"/>
<uri value="http://loinc.org/property/rad-guidance-for-approach"/>
<description value="Guidance for.Approach refers to the primary route of access used, such as percutaneous, transcatheter, or transhepatic."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-guidance-for-action"/>
<uri value="http://loinc.org/property/rad-guidance-for-action"/>
<description value="Guidance for.Action indicates the intervention performed, such as biopsy, aspiration, or ablation."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-guidance-for-object"/>
<uri value="http://loinc.org/property/rad-guidance-for-object"/>
<description value="Guidance for.Object specifies the target of the action, such as mass, abscess or cyst."/>
<type value="Coding"/>
</property>
<property>
<code value="rad-subject"/>
<uri value="http://loinc.org/property/rad-subject"/>
<description value="Subject is intended for use when there is a need to distinguish between the patient associated with an imaging study, and the target of the study."/>
<type value="Coding"/>
</property>
<!--
Document Ontology properties.
These apply only to terms in the LOINC Document Ontology File
Notes
Properties are specified as type "coding", which are represented by LOINC Part codes (LP-) and their display names.
The attribute names here use FHIR styled names rather than their original LOINC style names because those contain periods.
-->
<property>
<code value="document-kind"/>
<uri value="http://loinc.org/property/document-kind"/>
<description value="Characterizes the general structure of the document at a macro level."/>
<type value="Coding"/>
</property>
<property>
<code value="document-role"/>
<uri value="http://loinc.org/property/document-role"/>
<description value="Characterizes the training or professional level of the author of the document, but does not break down to specialty or subspecialty."/>
<type value="Coding"/>
</property>
<property>
<code value="document-setting"/>
<uri value="http://loinc.org/property/document-setting"/>
<description value="Setting is a modest extension of CMSs coarse definition of care settings, such as outpatient, hospital, etc. Setting is not equivalent to location, which typically has more locally defined meanings."/>
<type value="Coding"/>
</property>
<property>
<code value="document-subject-matter-domain"/>
<uri value="http://loinc.org/property/document-subject-matter-domain"/>
<description value="Characterizes the clinical domain that is the subject of the document. For example, Internal Medicine, Neurology, Physical Therapy, etc."/>
<type value="Coding"/>
</property>
<property>
<code value="document-type-of-service"/>
<uri value="http://loinc.org/property/document-type-of-service"/>
<description value="Characterizes the kind of service or activity provided to/for the patient (or other subject of the service) that is described in the document."/>
<type value="Coding"/>
</property>
<!-- Answer list related properties -->
<property>
<code value="answers-for"/>
<uri value="http://loinc.org/property/answers-for"/>
<description value="A LOINC Code for which this answer list is used."/>
<type value="Coding"/>
</property>
<!-- Note for future consideration. These are properties of LA codes in the context of a particular list. Not global properties.
<property>
<code value="sequence"/>
<uri value="http://loinc.org/property/sequence"/>
<description value="Sequence Number of a answer in a set of answers (LA- codes only)"/>
<type value="integer"/>
</property>
<property>
<code value="score"/>
<uri value="http://loinc.org/property/score"/>
<description value="Score assigned to an answer (LA- codes only)"/>
<type value="integer"/>
</property>
-->
</CodeSystem> </CodeSystem>

View File

@ -0,0 +1,83 @@
#################
### MANDATORY ###
#################
# Answer lists (ValueSets of potential answers/values for LOINC "questions")
## File must be present
loinc.answerlist.file=AccessoryFiles/AnswerFile/AnswerList.csv
# Answer list links (connects LOINC observation codes to answer list codes)
## File must be present
loinc.answerlist.link.file=AccessoryFiles/AnswerFile/LoincAnswerListLink.csv
# Document ontology
## File must be present
loinc.document.ontology.file=AccessoryFiles/DocumentOntology/DocumentOntology.csv
# LOINC codes
## File must be present
loinc.file=LoincTable/Loinc.csv
# LOINC hierarchy
## File must be present
loinc.hierarchy.file=AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv
# IEEE medical device codes
## File must be present
loinc.ieee.medical.device.code.mapping.table.file=AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv
# Imaging document codes
## File must be present
loinc.imaging.document.codes.file=AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv
# Part
## File must be present
loinc.part.file=AccessoryFiles/PartFile/Part.csv
# Part link
## File must be present
loinc.part.link.primary.file=AccessoryFiles/PartFile/LoincPartLink_Primary.csv
loinc.part.link.supplementary.file=AccessoryFiles/PartFile/LoincPartLink_Supplementary.csv
# Part related code mapping
## File must be present
loinc.part.related.code.mapping.file=AccessoryFiles/PartFile/PartRelatedCodeMapping.csv
# RSNA playbook
## File must be present
loinc.rsna.playbook.file=AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv
# Top 2000 codes - SI
## File must be present
loinc.top2000.common.lab.results.si.file=AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv
# Top 2000 codes - US
## File must be present
loinc.top2000.common.lab.results.us.file=AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv
# Universal lab order ValueSet
## File must be present
loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv
################
### OPTIONAL ###
################
# This is the version identifier for the answer list file
## Key may be omitted
loinc.answerlist.version=Beta.1
# This is the version identifier for uploaded ConceptMap resources
## Key may be omitted
loinc.conceptmap.version=Beta.1
# Group
## Default value if key not provided: AccessoryFiles/GroupFile/Group.csv
## File may be omitted
loinc.group.file=AccessoryFiles/GroupFile/Group.csv
# Group terms
## Default value if key not provided: AccessoryFiles/GroupFile/GroupLoincTerms.csv
## File may be omitted
loinc.group.terms.file=AccessoryFiles/GroupFile/GroupLoincTerms.csv
# Parent group
## Default value if key not provided: AccessoryFiles/GroupFile/ParentGroup.csv
## File may be omitted
loinc.parent.group.file=AccessoryFiles/GroupFile/ParentGroup.csv

View File

@ -41,6 +41,11 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
* starvation * starvation
*/ */
ourMaxThreads = (int) (Math.random() * 6.0) + 1; ourMaxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
ourMaxThreads = 1;
}
} }
private Exception myLastStackTrace; private Exception myLastStackTrace;

View File

@ -100,6 +100,11 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
* starvation * starvation
*/ */
int maxThreads = (int) (Math.random() * 6.0) + 1; int maxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
maxThreads = 1;
}
retVal.setMaxTotal(maxThreads); retVal.setMaxTotal(maxThreads);
return retVal; return retVal;

View File

@ -47,7 +47,11 @@ public class TestR4Config extends BaseJavaConfigR4 {
* starvation * starvation
*/ */
if (ourMaxThreads == null) { if (ourMaxThreads == null) {
ourMaxThreads = (int) (Math.random() * 6.0) + 3; ourMaxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
ourMaxThreads = 1;
}
} }
} }

View File

@ -41,6 +41,11 @@ public class TestR5Config extends BaseJavaConfigR5 {
*/ */
if (ourMaxThreads == null) { if (ourMaxThreads == null) {
ourMaxThreads = (int) (Math.random() * 6.0) + 1; ourMaxThreads = (int) (Math.random() * 6.0) + 1;
if ("true".equals(System.getProperty("single_db_connection"))) {
ourMaxThreads = 1;
}
} }
} }

View File

@ -43,6 +43,7 @@ import org.hibernate.HibernateException;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -133,7 +134,8 @@ public abstract class BaseJpaTest extends BaseTest {
@Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT) @Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT)
@Autowired @Autowired
private IValidationSupport myJpaPersistedValidationSupport; private IValidationSupport myJpaPersistedValidationSupport;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
@After @After
public void afterPerformCleanup() { public void afterPerformCleanup() {
@ -150,7 +152,9 @@ public abstract class BaseJpaTest extends BaseTest {
if (myJpaPersistedValidationSupport != null) { if (myJpaPersistedValidationSupport != null) {
ProxyUtil.getSingletonTarget(myJpaPersistedValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches(); ProxyUtil.getSingletonTarget(myJpaPersistedValidationSupport, JpaPersistedResourceValidationSupport.class).clearCaches();
} }
if (myFhirInstanceValidator != null) {
myFhirInstanceValidator.invalidateCaches();
}
} }

View File

@ -4,11 +4,13 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CareTeam;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
@ -145,7 +147,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
// Validate once // Validate once
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
myObservationDao.validate(obs, null, null, null, null, null, null); myObservationDao.validate(obs, null, null, null, null, null, null);
assertEquals(myCaptureQueriesListener.logSelectQueriesForCurrentThread(), 10, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logSelectQueriesForCurrentThread(), 9, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logUpdateQueriesForCurrentThread(), 0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logUpdateQueriesForCurrentThread(), 0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logInsertQueriesForCurrentThread(), 0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logInsertQueriesForCurrentThread(), 0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(myCaptureQueriesListener.logDeleteQueriesForCurrentThread(), 0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); assertEquals(myCaptureQueriesListener.logDeleteQueriesForCurrentThread(), 0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
@ -538,6 +540,53 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
} }
@Test
public void testSearchOnReverseInclude() {
Patient patient = new Patient();
patient.getMeta().addTag("http://system", "value1", "display");
patient.setId("P1");
patient.getNameFirstRep().setFamily("FAM1");
myPatientDao.update(patient);
patient = new Patient();
patient.setId("P2");
patient.getMeta().addTag("http://system", "value1", "display");
patient.getNameFirstRep().setFamily("FAM2");
myPatientDao.update(patient);
for (int i = 0; i < 3; i++) {
CareTeam ct = new CareTeam();
ct.setId("CT1-" + i);
ct.getMeta().addTag("http://system", "value11", "display");
ct.getSubject().setReference("Patient/P1");
myCareTeamDao.update(ct);
ct = new CareTeam();
ct.setId("CT2-" + i);
ct.getMeta().addTag("http://system", "value22", "display");
ct.getSubject().setReference("Patient/P2");
myCareTeamDao.update(ct);
}
SearchParameterMap map = SearchParameterMap
.newSynchronous()
.addRevInclude(CareTeam.INCLUDE_SUBJECT)
.setSort(new SortSpec(Patient.SP_NAME));
myCaptureQueriesListener.clear();
IBundleProvider outcome = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(
"Patient/P1", "CareTeam/CT1-0", "CareTeam/CT1-1","CareTeam/CT1-2",
"Patient/P2", "CareTeam/CT2-0", "CareTeam/CT2-1","CareTeam/CT2-2"
));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
}
@Test @Test
public void testTransactionWithMultipleReferences() { public void testTransactionWithMultipleReferences() {
Bundle input = new Bundle(); Bundle input = new Bundle();

View File

@ -876,6 +876,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
Observation obs = new Observation(); Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK"); obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
obs.setDevice(new Reference(devId)); obs.setDevice(new Reference(devId));
IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
DiagnosticReport dr = new DiagnosticReport();
dr.addResult().setReference(obsId.getValue());
dr.setStatus(DiagnosticReport.DiagnosticReportStatus.FINAL);
myObservationDao.create(obs, mySrd); myObservationDao.create(obs, mySrd);
} }
@ -896,6 +901,51 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty()); assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty());
} }
@Test
public void testHasParameterDouble() {
// Matching
IIdType pid0;
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("00");
patient.addName().setFamily("Tester").addGiven("Joe");
pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
obs.setSubject(new Reference(pid0));
IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
DiagnosticReport dr = new DiagnosticReport();
dr.addResult().setReference(obsId.getValue());
dr.setStatus(DiagnosticReport.DiagnosticReportStatus.FINAL);
myDiagnosticReportDao.create(dr, mySrd);
}
// Matching
{
Patient patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("001");
patient.addName().setFamily("Tester").addGiven("Joe");
IIdType pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
obs.setSubject(new Reference(pid1));
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
SearchParameterMap params = SearchParameterMap.newSynchronous();
// Double _has
params = new SearchParameterMap();
params.add("_has", new HasParam("Observation", "subject", "_has:DiagnosticReport:result:status", "final"));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), containsInAnyOrder(pid0.getValue()));
}
@Test @Test
public void testHasParameterChained() { public void testHasParameterChained() {
IIdType pid0; IIdType pid0;

View File

@ -1,12 +1,17 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -23,9 +28,29 @@ import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
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.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.AllergyIntolerance;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Group;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.r5.utils.IResourceValidator;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
@ -33,6 +58,7 @@ import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.AopTestUtils; import org.springframework.test.util.AopTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -45,6 +71,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class);
@ -56,6 +85,12 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvcc; private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvcc;
@Autowired @Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Autowired
private JpaValidationSupportChain myJpaValidationSupportChain;
@Autowired
private PlatformTransactionManager myTransactionManager;
@Autowired
private ValidationSettings myValidationSettings;
/** /**
* Create a loinc valueset that expands to more results than the expander is willing to do * Create a loinc valueset that expands to more results than the expander is willing to do
@ -155,13 +190,205 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
}
@Test
public void testValidateProfileTargetType_PolicyCheckValid() throws IOException {
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_VALID);
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
CodeSystem cs = new CodeSystem();
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://loinc.org");
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
myCodeSystemDao.create(cs);
Group group = new Group();
group.setId("ABC");
group.setActive(true);
myGroupDao.update(group);
Patient patient = new Patient();
patient.setId("DEF");
patient.setActive(true);
myPatientDao.update(patient);
Practitioner practitioner = new Practitioner();
practitioner.setId("P");
practitioner.setActive(true);
myPractitionerDao.update(practitioner);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.addPerformer(new Reference("Practitioner/P"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
// Target of wrong type
obs.setSubject(new Reference("Group/ABC"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Invalid Resource target type. Found Group, but expected one of ([Patient])", oo.getIssueFirstRep().getDiagnostics());
// Target of right type
obs.setSubject(new Reference("Patient/DEF"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
} }
@Test
public void testValidateProfileTargetType_PolicyCheckExistsAndType() throws IOException {
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE);
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
CodeSystem cs = new CodeSystem();
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://loinc.org");
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
myCodeSystemDao.create(cs);
Group group = new Group();
group.setId("ABC");
group.setActive(true);
myGroupDao.update(group);
Patient patient = new Patient();
patient.setId("DEF");
patient.setActive(true);
myPatientDao.update(patient);
Practitioner practitioner = new Practitioner();
practitioner.setId("P");
practitioner.setActive(true);
myPractitionerDao.update(practitioner);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.addPerformer(new Reference("Practitioner/P"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
// Target of wrong type
obs.setSubject(new Reference("Group/ABC"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to find matching profile for Group/ABC (by type) among choices: ; [CanonicalType[http://hl7.org/fhir/StructureDefinition/Patient]]", oo.getIssueFirstRep().getDiagnostics());
// Target of right type
obs.setSubject(new Reference("Patient/DEF"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
}
@Test
public void testValidateProfileTargetType_PolicyCheckExists() throws IOException {
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_EXISTS);
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
CodeSystem cs = new CodeSystem();
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://loinc.org");
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
myCodeSystemDao.create(cs);
Group group = new Group();
group.setId("ABC");
group.setActive(true);
myGroupDao.update(group);
Patient patient = new Patient();
patient.setId("DEF");
patient.setActive(true);
myPatientDao.update(patient);
Practitioner practitioner = new Practitioner();
practitioner.setId("P");
practitioner.setActive(true);
myPractitionerDao.update(practitioner);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.addPerformer(new Reference("Practitioner/P"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
// Non-existent target
obs.setSubject(new Reference("Group/123"));
OperationOutcome oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
// Target of wrong type
obs.setSubject(new Reference("Group/ABC"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
// Target of right type
obs.setSubject(new Reference("Patient/DEF"));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
}
/** /**
* Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems * Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems
* * <p>
* We should generate a warning if a code can't be found but the codesystem is a fragment * We should generate a warning if a code can't be found but the codesystem is a fragment
*/ */
@Test @Test
@ -219,7 +446,6 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} }
} }
/** /**
* Create a loinc valueset that expands to more results than the expander is willing to do * Create a loinc valueset that expands to more results than the expander is willing to do
* in memory, and make sure we can still validate correctly, even if we're using * in memory, and make sure we can still validate correctly, even if we're using
@ -303,6 +529,115 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} }
/**
* Make sure that we do something sane when validating throws an unexpected exception
*/
@Test
public void testValidate_ValidationSupportThrowsException() {
IValidationSupport validationSupport = mock(IValidationSupport.class);
when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenAnswer(t -> {
// This will fail with a constraint error
try {
myResourceTableDao.save(new ResourceTable());
myResourceTableDao.flush();
} catch (Exception e) {
ourLog.info("Hit expected exception: {}", e.toString());
}
return null;
});
when(validationSupport.getFhirContext()).thenReturn(myFhirCtx);
myJpaValidationSupportChain.addValidationSupport(0, validationSupport);
try {
Observation obs = new Observation();
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
} finally {
myJpaValidationSupportChain.removeValidationSupport(validationSupport);
}
}
/**
* Make sure that we do something sane when validating throws an unexpected exception
*/
@Test
@Ignore
public void testValidate_TermSvcHasDatabaseRollback() {
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(() -> {
try {
myResourceTableDao.save(new ResourceTable());
myResourceTableDao.flush();
} catch (Exception e) {
ourLog.info("Hit expected exception: {}", e.toString());
}
});
Observation obs = new Observation();
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
}
/**
* Make sure that we do something sane when validating throws an unexpected exception
*/
@Test
public void testValidate_TermSvcHasNpe() {
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(() -> {
throw new NullPointerException("MY ERROR");
});
Observation obs = new Observation();
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE99999").setDisplay("Display 3");
try {
validateAndReturnOutcome(obs);
fail();
} catch (NullPointerException e) {
assertEquals("MY ERROR", e.getMessage());
}
}
@Test @Test
public void testValidateCodeableConceptWithNoSystem() { public void testValidateCodeableConceptWithNoSystem() {
AllergyIntolerance allergy = new AllergyIntolerance(); AllergyIntolerance allergy = new AllergyIntolerance();
@ -605,6 +940,10 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setMaximumExpansionSize(DaoConfig.DEFAULT_MAX_EXPANSION_SIZE); myDaoConfig.setMaximumExpansionSize(DaoConfig.DEFAULT_MAX_EXPANSION_SIZE);
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets()); myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(null);
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.IGNORE);
} }
@Test @Test

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao; import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
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.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.ProxyUtil; import ca.uhn.fhir.test.utilities.ProxyUtil;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
@ -30,9 +31,9 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
public class IgInstallerTestDstu3 extends BaseJpaDstu3Test { public class IgInstallerDstu3Test extends BaseJpaDstu3Test {
private static final Logger ourLog = LoggerFactory.getLogger(IgInstallerTestDstu3.class); private static final Logger ourLog = LoggerFactory.getLogger(IgInstallerDstu3Test.class);
@Autowired @Autowired
private DaoConfig daoConfig; private DaoConfig daoConfig;
@Autowired @Autowired
@ -185,7 +186,7 @@ public class IgInstallerTestDstu3 extends BaseJpaDstu3Test {
); );
fail(); fail();
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
assertEquals("", e.getMessage()); assertEquals("Package ID nictiz.fhir.nl.stu3.questionnaires doesn't match expected: blah", e.getMessage());
} }
} }
@ -199,8 +200,8 @@ public class IgInstallerTestDstu3 extends BaseJpaDstu3Test {
.setPackageUrl("http://localhost:" + myPort + "/foo.tgz") .setPackageUrl("http://localhost:" + myPort + "/foo.tgz")
); );
fail(); fail();
} catch (InvalidRequestException e) { } catch (ResourceNotFoundException e) {
assertEquals("", e.getMessage()); assertEquals("Received HTTP 404 from URL: http://localhost:" + myPort + "/foo.tgz", e.getMessage());
} }
} }

View File

@ -19,7 +19,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
@Test @Test
public void testSavePackage() throws IOException { public void testSavePackage() throws IOException {
try (InputStream stream = IgInstallerTestDstu3.class.getResourceAsStream("/packages/basisprofil.de.tar.gz")) { try (InputStream stream = IgInstallerDstu3Test.class.getResourceAsStream("/packages/basisprofil.de.tar.gz")) {
myPackageCacheManager.addPackageToCache("basisprofil.de", "0.2.40", stream, "basisprofil.de"); myPackageCacheManager.addPackageToCache("basisprofil.de", "0.2.40", stream, "basisprofil.de");
} }

View File

@ -492,7 +492,8 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
addFile(zos, LOINC_GROUP_FILE_DEFAULT.getCode()); addFile(zos, LOINC_GROUP_FILE_DEFAULT.getCode());
addFile(zos, LOINC_GROUP_TERMS_FILE_DEFAULT.getCode()); addFile(zos, LOINC_GROUP_TERMS_FILE_DEFAULT.getCode());
addFile(zos, LOINC_PARENT_GROUP_FILE_DEFAULT.getCode()); addFile(zos, LOINC_PARENT_GROUP_FILE_DEFAULT.getCode());
addFile(zos, LOINC_PART_LINK_FILE_DEFAULT.getCode()); addFile(zos, LOINC_PART_LINK_FILE_PRIMARY_DEFAULT.getCode());
addFile(zos, LOINC_PART_LINK_FILE_SUPPLEMENTARY_DEFAULT.getCode());
addFile(zos, LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()); addFile(zos, LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode());
addFile(zos, LOINC_DOCUMENT_ONTOLOGY_FILE_DEFAULT.getCode()); addFile(zos, LOINC_DOCUMENT_ONTOLOGY_FILE_DEFAULT.getCode());
addFile(zos, LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()); addFile(zos, LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode());

View File

@ -301,6 +301,103 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
} }
@Test
public void testExpandByIdWithPreExpansionWithOffset() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "offset", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
}
@Test
public void testExpandByIdWithPreExpansionWithCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(0, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(0, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByIdWithPreExpansionWithOffsetAndCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "offset", new IntegerType(1))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test @Test
public void testExpandByIdWithFilter() throws Exception { public void testExpandByIdWithFilter() throws Exception {
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST); loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
@ -402,6 +499,106 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
} }
@Test
public void testExpandByUrlWithPreExpansionWithOffset() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("offset", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
}
@Test
public void testExpandByUrlWithPreExpansionWithCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(0, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(0, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByUrlWithPreExpansionWithOffsetAndCount() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("offset", new IntegerType(1))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test @Test
public void testExpandByUrlWithPreExpansionAndBogusUrl() throws Exception { public void testExpandByUrlWithPreExpansionAndBogusUrl() throws Exception {
myDaoConfig.setPreExpandValueSets(true); myDaoConfig.setPreExpandValueSets(true);
@ -448,7 +645,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
public void testExpandByValueSetWithPreExpansion() throws IOException { public void testExpandByValueSetWithPreExpansion() throws IOException {
myDaoConfig.setPreExpandValueSets(true); myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystem(HTTPVerb.POST); loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
@ -469,6 +666,112 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
} }
@Test
public void testExpandByValueSetWithPreExpansionWithOffset() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("offset", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1000, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(23, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(0).getSystem());
assertEquals("11378-7", expanded.getExpansion().getContains().get(0).getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContains().get(0).getDisplay());
assertEquals("http://acme.org", expanded.getExpansion().getContains().get(1).getSystem());
assertEquals("8493-9", expanded.getExpansion().getContains().get(1).getCode());
assertEquals("Systolic blood pressure 10 hour minimum", expanded.getExpansion().getContains().get(1).getDisplay());
}
@Test
public void testExpandByValueSetWithPreExpansionWithCount() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(0, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(0, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("8450-9", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure--expiration", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test
public void testExpandByValueSetWithPreExpansionWithOffsetAndCount() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
loadAndPersistCodeSystemAndValueSet(HTTPVerb.POST);
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("offset", new IntegerType(1))
.andParameter("count", new IntegerType(1))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertEquals(24, expanded.getExpansion().getTotal());
assertEquals(1, expanded.getExpansion().getOffset());
assertEquals("offset", expanded.getExpansion().getParameter().get(0).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expanded.getExpansion().getParameter().get(1).getName());
assertEquals(1, expanded.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(1, expanded.getExpansion().getContains().size());
assertEquals("http://acme.org", expanded.getExpansion().getContainsFirstRep().getSystem());
assertEquals("11378-7", expanded.getExpansion().getContainsFirstRep().getCode());
assertEquals("Systolic blood pressure at First encounter", expanded.getExpansion().getContainsFirstRep().getDisplay());
}
@Test @Test
public void testExpandInlineVsAgainstBuiltInCs() { public void testExpandInlineVsAgainstBuiltInCs() {
createLocalVsPointingAtBuiltInCodeSystem(); createLocalVsPointingAtBuiltInCodeSystem();

View File

@ -86,7 +86,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
Set<String> codes = toExpandedCodes(expanded); Set<String> codes = toExpandedCodes(expanded);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded));
ourLog.info("Codes: {}", codes); ourLog.info("Codes: {}", codes);
assertThat(codes, containsInAnyOrder("10019-8", "10013-1", "10014-9", "10016-4", "17788-1", "10000-8", "10017-2", "10015-6", "10020-6", "10018-0")); assertThat(codes, containsInAnyOrder("10013-1"));
// Search by display name // Search by display name
input = new ValueSet(); input = new ValueSet();
@ -101,7 +101,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
expanded = myValueSetDao.expand(input, null); expanded = myValueSetDao.expand(input, null);
codes = toExpandedCodes(expanded); codes = toExpandedCodes(expanded);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertThat(codes, containsInAnyOrder("10019-8", "10013-1", "10014-9", "10016-4", "17788-1", "10000-8", "10017-2", "10015-6", "10020-6", "10018-0")); assertThat(codes, containsInAnyOrder("10013-1"));
// Search by something that doesn't match // Search by something that doesn't match
input = new ValueSet(); input = new ValueSet();
@ -149,7 +149,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
Set<String> codes = toExpandedCodes(expanded); Set<String> codes = toExpandedCodes(expanded);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded));
ourLog.info("Codes: {}", codes); ourLog.info("Codes: {}", codes);
assertThat(codes, containsInAnyOrder("10019-8", "10013-1", "10014-9", "10000-8", "10016-4", "10017-2", "10015-6", "10020-6", "10018-0")); assertThat(codes, containsInAnyOrder("10013-1"));
} }
@Test @Test
@ -188,7 +188,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
myLoader.loadLoinc(files.getFiles(), mySrd); myLoader.loadLoinc(files.getFiles(), mySrd);
IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("17788-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd);
Parameters parameters = (Parameters) result.toParameters(myFhirCtx, null); Parameters parameters = (Parameters) result.toParameters(myFhirCtx, null);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters)); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters));
@ -196,8 +196,8 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
Optional<Coding> propertyValue = findProperty(parameters, "COMPONENT"); Optional<Coding> propertyValue = findProperty(parameters, "COMPONENT");
assertTrue(propertyValue.isPresent()); assertTrue(propertyValue.isPresent());
assertEquals(ITermLoaderSvc.LOINC_URI, propertyValue.get().getSystem()); assertEquals(ITermLoaderSvc.LOINC_URI, propertyValue.get().getSystem());
assertEquals("LP19258-0", propertyValue.get().getCode()); assertEquals("LP31101-6", propertyValue.get().getCode());
assertEquals("Large unstained cells/100 leukocytes", propertyValue.get().getDisplay()); assertEquals("R' wave amplitude.lead I", propertyValue.get().getDisplay());
} }
@Test @Test

View File

@ -76,18 +76,20 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest {
// Normal LOINC code // Normal LOINC code
code = concepts.get("10013-1"); code = concepts.get("10013-1");
assertEquals("10013-1", code.getCode()); assertEquals("10013-1", code.getCode());
// Coding Property
assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem()); assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem());
assertEquals("LP6802-5", code.getCodingProperties("PROPERTY").get(0).getCode()); assertEquals("LP6802-5", code.getCodingProperties("PROPERTY").get(0).getCode());
assertEquals("Elpot", code.getCodingProperties("PROPERTY").get(0).getDisplay()); assertEquals("Elpot", code.getCodingProperties("PROPERTY").get(0).getDisplay());
assertEquals("EKG.MEAS", code.getStringProperty("CLASS")); // String Property
assertEquals("2", code.getStringProperty("CLASSTYPE"));
assertEquals("R' wave amplitude in lead I", code.getDisplay()); assertEquals("R' wave amplitude in lead I", code.getDisplay());
// Coding Property from Part File
assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("TIME_ASPCT").get(0).getSystem());
assertEquals("LP6960-1", code.getCodingProperties("TIME_ASPCT").get(0).getCode());
assertEquals("Pt", code.getCodingProperties("TIME_ASPCT").get(0).getDisplay());
// Code with component that has a divisor // Code with component that has a divisor
code = concepts.get("17788-1"); code = concepts.get("17788-1");
assertEquals("17788-1", code.getCode()); assertEquals("17788-1", code.getCode());
assertEquals(1, code.getCodingProperties("COMPONENT").size());
assertEquals("http://loinc.org", code.getCodingProperties("COMPONENT").get(0).getSystem());
assertEquals("LP19258-0", code.getCodingProperties("COMPONENT").get(0).getCode());
// LOINC code with answer // LOINC code with answer
code = concepts.get("61438-8"); code = concepts.get("61438-8");
@ -379,7 +381,8 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest {
theFiles.addFileZip("/loinc/", LOINC_ANSWERLIST_LINK_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_ANSWERLIST_LINK_FILE_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_ANSWERLIST_LINK_DUPLICATE_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_ANSWERLIST_LINK_DUPLICATE_FILE_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_PART_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_PART_FILE_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_PART_LINK_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_PART_LINK_FILE_PRIMARY_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_PART_LINK_FILE_SUPPLEMENTARY_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_DOCUMENT_ONTOLOGY_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_DOCUMENT_ONTOLOGY_FILE_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode());
@ -413,7 +416,10 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest {
assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem()); assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem());
assertEquals("LP6802-5", code.getCodingProperties("PROPERTY").get(0).getCode()); assertEquals("LP6802-5", code.getCodingProperties("PROPERTY").get(0).getCode());
assertEquals("Elpot", code.getCodingProperties("PROPERTY").get(0).getDisplay()); assertEquals("Elpot", code.getCodingProperties("PROPERTY").get(0).getDisplay());
assertEquals("EKG.MEAS", code.getStringProperty("CLASS")); assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem());
assertEquals("LP6802-5", code.getCodingProperties("PROPERTY").get(0).getCode());
assertEquals("Elpot", code.getCodingProperties("PROPERTY").get(0).getDisplay());
assertEquals("2", code.getStringProperty("CLASSTYPE"));
assertEquals("R' wave amplitude in lead I", code.getDisplay()); assertEquals("R' wave amplitude in lead I", code.getDisplay());
// Codes with parent and child properties // Codes with parent and child properties

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.jpa.util;
import org.junit.Test;
import static org.junit.Assert.fail;
public class JpaClasspathTest {
/**
* Make sure no dependencies start bringing in log4j - This makes hibernate decide to start using log4j instead of
* slf4j which is super annoying..
*/
@Test
public void testNoLog4jOnClasspath() {
try {
Class.forName("org.apache.logging.log4j.status.StatusLogger");
fail("org.apache.logging.log4j.status.StatusLogger" + " found on classpath - Make sure log4j isn't being introduced");
} catch (ClassNotFoundException theE) {
// good
}
}
}

View File

@ -1,10 +0,0 @@
"LoincNumber","LongCommonName","PartNumber","PartName","PartTypeName","LinkTypeName"
"10000-8","R wave duration in lead AVR","LP31088-5","R wave duration.lead AVR","COMPONENT","Primary"
"10000-8","R wave duration in lead AVR","LP6244-0","EKG","METHOD","Primary"
"10000-8","R wave duration in lead AVR","LP6879-3","Time","PROPERTY","Primary"
"10000-8","R wave duration in lead AVR","LP6960-1","Pt","TIME","Primary"
"10000-8","R wave duration in lead AVR","LP7289-4","Heart","SYSTEM","Primary"
"10000-8","R wave duration in lead AVR","LP7753-9","Qn","SCALE","Primary"
"10000-8","R wave duration in lead AVR","LP7795-0","EKG.MEAS","CLASS","Primary"
"10000-8","R wave duration in lead AVR","LP14259-3","Lead","COMPONENT","Search"
"10000-8","R wave duration in lead AVR","LP14744-4","Duration","COMPONENT","Search"
1 LoincNumber LongCommonName PartNumber PartName PartTypeName LinkTypeName
2 10000-8 R wave duration in lead AVR LP31088-5 R wave duration.lead AVR COMPONENT Primary
3 10000-8 R wave duration in lead AVR LP6244-0 EKG METHOD Primary
4 10000-8 R wave duration in lead AVR LP6879-3 Time PROPERTY Primary
5 10000-8 R wave duration in lead AVR LP6960-1 Pt TIME Primary
6 10000-8 R wave duration in lead AVR LP7289-4 Heart SYSTEM Primary
7 10000-8 R wave duration in lead AVR LP7753-9 Qn SCALE Primary
8 10000-8 R wave duration in lead AVR LP7795-0 EKG.MEAS CLASS Primary
9 10000-8 R wave duration in lead AVR LP14259-3 Lead COMPONENT Search
10 10000-8 R wave duration in lead AVR LP14744-4 Duration COMPONENT Search

View File

@ -0,0 +1,7 @@
"LoincNumber","LongCommonName" ,"PartNumber","PartName" ,"PartCodeSystem" ,"PartTypeName","LinkTypeName","Property"
"10013-1" ,"R' wave amplitude in lead I","LP31101-6" ,"R' wave amplitude.lead I","http://loinc.org","COMPONENT" ,"Primary" ,"http://loinc.org/property/COMPONENT"
"10013-1" ,"R' wave amplitude in lead I","LP6802-5" ,"Elpot" ,"http://loinc.org","PROPERTY" ,"Primary" ,"http://loinc.org/property/PROPERTY"
"10013-1" ,"R' wave amplitude in lead I","LP6960-1" ,"Pt" ,"http://loinc.org","TIME" ,"Primary" ,"http://loinc.org/property/TIME_ASPCT"
"10013-1" ,"R' wave amplitude in lead I","LP7289-4" ,"Heart" ,"http://loinc.org","SYSTEM" ,"Primary" ,"http://loinc.org/property/SYSTEM"
"10013-1" ,"R' wave amplitude in lead I","LP7753-9" ,"Qn" ,"http://loinc.org","SCALE" ,"Primary" ,"http://loinc.org/property/SCALE_TYP"
"10013-1" ,"R' wave amplitude in lead I","LP6244-0" ,"EKG" ,"http://loinc.org","METHOD" ,"Primary" ,"http://loinc.org/property/METHOD_TYP"
Can't render this file because it contains an unexpected character in line 1 and column 30.

View File

@ -0,0 +1,13 @@
"LoincNumber","LongCommonName" ,"PartNumber","PartName" ,"PartCodeSystem" ,"PartTypeName","LinkTypeName" ,"Property"
"10013-1" ,"R' wave amplitude in lead I","LP31101-6" ,"R' wave amplitude.lead I","http://loinc.org","COMPONENT" ,"DetailedModel" ,"http://loinc.org/property/analyte"
"10013-1" ,"R' wave amplitude in lead I","LP6802-5" ,"Elpot" ,"http://loinc.org","PROPERTY" ,"DetailedModel" ,"http://loinc.org/property/PROPERTY"
"10013-1" ,"R' wave amplitude in lead I","LP6960-1" ,"Pt" ,"http://loinc.org","TIME" ,"DetailedModel" ,"http://loinc.org/property/time-core"
"10013-1" ,"R' wave amplitude in lead I","LP7289-4" ,"Heart" ,"http://loinc.org","SYSTEM" ,"DetailedModel" ,"http://loinc.org/property/system-core"
"10013-1" ,"R' wave amplitude in lead I","LP7753-9" ,"Qn" ,"http://loinc.org","SCALE" ,"DetailedModel" ,"http://loinc.org/property/SCALE_TYP"
"10013-1" ,"R' wave amplitude in lead I","LP6244-0" ,"EKG" ,"http://loinc.org","METHOD" ,"DetailedModel" ,"http://loinc.org/property/METHOD_TYP"
"10013-1" ,"R' wave amplitude in lead I","LP31101-6" ,"R' wave amplitude.lead I","http://loinc.org","COMPONENT" ,"SyntaxEnhancement","http://loinc.org/property/analyte-core"
"10013-1" ,"R' wave amplitude in lead I","LP190563-9","Cardiology" ,"http://loinc.org","CLASS" ,"Metadata" ,"http://loinc.org/property/category"
"10013-1" ,"R' wave amplitude in lead I","LP29708-2" ,"Cardiology" ,"http://loinc.org","CLASS" ,"Metadata" ,"http://loinc.org/property/category"
"10013-1" ,"R' wave amplitude in lead I","LP7787-7" ,"Clinical" ,"http://loinc.org","CLASS" ,"Metadata" ,"http://loinc.org/property/category"
"10013-1" ,"R' wave amplitude in lead I","LP7795-0" ,"EKG measurements" ,"http://loinc.org","CLASS" ,"Metadata" ,"http://loinc.org/property/category"
"10013-1" ,"R' wave amplitude in lead I","LP7795-0" ,"EKG.MEAS" ,"http://loinc.org","CLASS" ,"Metadata" ,"http://loinc.org/property/CLASS"
Can't render this file because it contains an unexpected character in line 1 and column 30.

View File

@ -3,68 +3,57 @@
################# #################
# Answer lists (ValueSets of potential answers/values for LOINC "questions") # Answer lists (ValueSets of potential answers/values for LOINC "questions")
## Default value if key not provided: AccessoryFiles/AnswerFile/AnswerList.csv
## File must be present ## File must be present
loinc.answerlist.file=AccessoryFiles/AnswerFile/AnswerList.csv loinc.answerlist.file=AccessoryFiles/AnswerFile/AnswerList.csv
# Answer list links (connects LOINC observation codes to answer list codes) # Answer list links (connects LOINC observation codes to answer list codes)
## Default value if key not provided: AccessoryFiles/AnswerFile/LoincAnswerListLink.csv
## File must be present ## File must be present
loinc.answerlist.link.file=AccessoryFiles/AnswerFile/LoincAnswerListLink.csv loinc.answerlist.link.file=AccessoryFiles/AnswerFile/LoincAnswerListLink.csv
# Document ontology # Document ontology
## Default value if key not provided: AccessoryFiles/DocumentOntology/DocumentOntology.csv
## File must be present ## File must be present
loinc.document.ontology.file=AccessoryFiles/DocumentOntology/DocumentOntology.csv loinc.document.ontology.file=AccessoryFiles/DocumentOntology/DocumentOntology.csv
# LOINC codes # LOINC codes
## Default value if key not provided: LoincTable/Loinc.csv
## File must be present ## File must be present
loinc.file=LoincTable/Loinc.csv loinc.file=LoincTable/Loinc.csv
# LOINC hierarchy # LOINC hierarchy
## Default value if key not provided: AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv
## File must be present ## File must be present
loinc.hierarchy.file=AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv loinc.hierarchy.file=AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv
# IEEE medical device codes # IEEE medical device codes
## Default value if key not provided: AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv
## File must be present ## File must be present
loinc.ieee.medical.device.code.mapping.table.file=AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv loinc.ieee.medical.device.code.mapping.table.file=AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv
# Imaging document codes # Imaging document codes
## Default value if key not provided: AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv
## File must be present ## File must be present
loinc.imaging.document.codes.file=AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv loinc.imaging.document.codes.file=AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv
# Part # Part
## Default value if key not provided: AccessoryFiles/PartFile/Part.csv
## File must be present ## File must be present
loinc.part.file=AccessoryFiles/PartFile/Part.csv loinc.part.file=AccessoryFiles/PartFile/Part.csv
# Part link # Part link
## Default value if key not provided: AccessoryFiles/PartFile/LoincPartLink.csv
## File must be present ## File must be present
loinc.part.link.file=AccessoryFiles/PartFile/LoincPartLink.csv loinc.part.link.primary.file=AccessoryFiles/PartFile/LoincPartLink_Primary.csv
loinc.part.link.supplementary.file=AccessoryFiles/PartFile/LoincPartLink_Supplementary.csv
# Part related code mapping # Part related code mapping
## Default value if key not provided: AccessoryFiles/PartFile/PartRelatedCodeMapping.csv
## File must be present ## File must be present
loinc.part.related.code.mapping.file=AccessoryFiles/PartFile/PartRelatedCodeMapping.csv loinc.part.related.code.mapping.file=AccessoryFiles/PartFile/PartRelatedCodeMapping.csv
# RSNA playbook # RSNA playbook
## Default value if key not provided: AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv
## File must be present ## File must be present
loinc.rsna.playbook.file=AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv loinc.rsna.playbook.file=AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv
# Top 2000 codes - SI # Top 2000 codes - SI
## Default value if key not provided: AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv
## File must be present ## File must be present
loinc.top2000.common.lab.results.si.file=AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv loinc.top2000.common.lab.results.si.file=AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv
# Top 2000 codes - US # Top 2000 codes - US
## Default value if key not provided: AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv
## File must be present ## File must be present
loinc.top2000.common.lab.results.us.file=AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv loinc.top2000.common.lab.results.us.file=AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv
# Universal lab order ValueSet # Universal lab order ValueSet
## Default value if key not provided: AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv
## File must be present ## File must be present
loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv

View File

@ -317,6 +317,47 @@ public class JdbcUtils {
} }
} }
/**
* Retrieve names of foreign keys that reference a specified foreign key column.
*/
public static Set<String> getForeignKeysForColumn(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theForeignKeyColumn, String theForeignTable) throws SQLException {
DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
try (Connection connection = dataSource.getConnection()) {
return theConnectionProperties.getTxTemplate().execute(t -> {
DatabaseMetaData metadata;
try {
metadata = connection.getMetaData();
String catalog = connection.getCatalog();
String schema = connection.getSchema();
List<String> parentTables = new ArrayList<>();
parentTables.addAll(JdbcUtils.getTableNames(theConnectionProperties));
String foreignTable = massageIdentifier(metadata, theForeignTable);
Set<String> fkNames = new HashSet<>();
for (String nextParentTable : parentTables) {
ResultSet indexes = metadata.getCrossReference(catalog, schema, nextParentTable, catalog, schema, foreignTable);
while (indexes.next()) {
if (theForeignKeyColumn.equals(indexes.getString("FKCOLUMN_NAME"))) {
String fkName = indexes.getString("FK_NAME");
fkName = toUpperCase(fkName, Locale.US);
fkNames.add(fkName);
}
}
}
return fkNames;
} catch (SQLException e) {
throw new InternalErrorException(e);
}
});
}
}
/** /**
* Retrieve all index names * Retrieve all index names
*/ */

View File

@ -53,9 +53,12 @@ public class AddColumnTask extends BaseTableColumnTypeTask {
String sql; String sql;
switch (getDriverType()) { switch (getDriverType()) {
case MYSQL_5_7:
// Quote the column name as "SYSTEM" is a reserved word in MySQL
sql = "alter table " + getTableName() + " add column `" + getColumnName() + "` " + typeStatement;
break;
case DERBY_EMBEDDED: case DERBY_EMBEDDED:
case MARIADB_10_1: case MARIADB_10_1:
case MYSQL_5_7:
case POSTGRES_9_4: case POSTGRES_9_4:
sql = "alter table " + getTableName() + " add column " + getColumnName() + " " + typeStatement; sql = "alter table " + getTableName() + " add column " + getColumnName() + " " + typeStatement;
break; break;

View File

@ -78,7 +78,8 @@ public class AddForeignKeyTask extends BaseTableColumnTask {
switch (getDriverType()) { switch (getDriverType()) {
case MARIADB_10_1: case MARIADB_10_1:
case MYSQL_5_7: case MYSQL_5_7:
sql = "alter table " + getTableName() + " add constraint " + myConstraintName + " foreign key (" + getColumnName() + ") references " + myForeignTableName + " (" + myForeignColumnName + ")"; // Quote the column names as "SYSTEM" is a reserved word in MySQL
sql = "alter table " + getTableName() + " add constraint " + myConstraintName + " foreign key (`" + getColumnName() + "`) references " + myForeignTableName + " (`" + myForeignColumnName + "`)";
break; break;
case POSTGRES_9_4: case POSTGRES_9_4:
case DERBY_EMBEDDED: case DERBY_EMBEDDED:

View File

@ -20,12 +20,14 @@ package ca.uhn.fhir.jpa.migrate.taskdef;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.JdbcUtils; import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.Language;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import java.util.Set; import java.util.Set;
public class DropColumnTask extends BaseTableColumnTask { public class DropColumnTask extends BaseTableColumnTask {
@ -50,6 +52,20 @@ public class DropColumnTask extends BaseTableColumnTask {
return; return;
} }
if(getDriverType().equals(DriverTypeEnum.MYSQL_5_7)) {
// Some DBs such as MYSQL require that foreign keys depending on the column be dropped before the column itself is dropped.
logInfo(ourLog, "Dropping any foreign keys on table {} depending on column {}", getTableName(), getColumnName());
Set<String> foreignKeys = JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), getColumnName(), getTableName());
if(foreignKeys != null) {
for (String foreignKey:foreignKeys) {
List<String> dropFkSqls = DropForeignKeyTask.generateSql(getTableName(), foreignKey, getDriverType());
for(String dropFkSql : dropFkSqls) {
executeSql(getTableName(), dropFkSql);
}
}
}
}
String tableName = getTableName(); String tableName = getTableName();
String columnName = getColumnName(); String columnName = getColumnName();
String sql = createSql(tableName, columnName); String sql = createSql(tableName, columnName);

View File

@ -102,8 +102,7 @@ public class DropForeignKeyTask extends BaseTableTask {
switch (theDriverType) { switch (theDriverType) {
case MYSQL_5_7: case MYSQL_5_7:
// Lousy MYQL.... // Lousy MYQL....
sqls.add("alter table " + theTableName + " drop constraint " + theConstraintName); sqls.add("alter table " + theTableName + " drop foreign key " + theConstraintName);
sqls.add("alter table " + theTableName + " drop index " + theConstraintName);
break; break;
case MARIADB_10_1: case MARIADB_10_1:
case POSTGRES_9_4: case POSTGRES_9_4:

View File

@ -107,6 +107,9 @@ public class DropIndexTask extends BaseTableTask {
// Drop constraint // Drop constraint
switch (theDriverType) { switch (theDriverType) {
case MYSQL_5_7: case MYSQL_5_7:
// Need to quote the index name as the word "PRIMARY" is reserved in MySQL
sql.add("alter table " + theTableName + " drop index `" + theIndexName + "`");
break;
case MARIADB_10_1: case MARIADB_10_1:
sql.add("alter table " + theTableName + " drop index " + theIndexName); sql.add("alter table " + theTableName + " drop index " + theIndexName);
break; break;
@ -114,16 +117,14 @@ public class DropIndexTask extends BaseTableTask {
sql.add("drop index " + theIndexName); sql.add("drop index " + theIndexName);
break; break;
case DERBY_EMBEDDED: case DERBY_EMBEDDED:
case ORACLE_12C:
case MSSQL_2012:
sql.add("alter table " + theTableName + " drop constraint " + theIndexName); sql.add("alter table " + theTableName + " drop constraint " + theIndexName);
break; break;
case POSTGRES_9_4: case POSTGRES_9_4:
sql.add("alter table " + theTableName + " drop constraint if exists " + theIndexName + " cascade"); sql.add("alter table " + theTableName + " drop constraint if exists " + theIndexName + " cascade");
sql.add("drop index if exists " + theIndexName + " cascade"); sql.add("drop index if exists " + theIndexName + " cascade");
break; break;
case ORACLE_12C:
case MSSQL_2012:
sql.add("alter table " + theTableName + " drop constraint " + theIndexName);
break;
} }
} else { } else {
// Drop index // Drop index

View File

@ -99,7 +99,8 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
break; break;
case MARIADB_10_1: case MARIADB_10_1:
case MYSQL_5_7: case MYSQL_5_7:
sql = "alter table " + getTableName() + " modify column " + getColumnName() + " " + type + notNull; // Quote the column name as "SYSTEM" is a reserved word in MySQL
sql = "alter table " + getTableName() + " modify column `" + getColumnName() + "` " + type + notNull;
break; break;
case POSTGRES_9_4: case POSTGRES_9_4:
if (!alreadyOfCorrectType) { if (!alreadyOfCorrectType) {

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.migrate.taskdef;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.JdbcUtils; import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -30,6 +32,7 @@ import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import java.util.Set; import java.util.Set;
public class RenameColumnTask extends BaseTableTask { public class RenameColumnTask extends BaseTableTask {
@ -40,6 +43,8 @@ public class RenameColumnTask extends BaseTableTask {
private boolean myIsOkayIfNeitherColumnExists; private boolean myIsOkayIfNeitherColumnExists;
private boolean myDeleteTargetColumnFirstIfBothExist; private boolean myDeleteTargetColumnFirstIfBothExist;
private boolean mySimulateMySQLForTest = false;
public RenameColumnTask(String theProductVersion, String theSchemaVersion) { public RenameColumnTask(String theProductVersion, String theSchemaVersion) {
super(theProductVersion, theSchemaVersion); super(theProductVersion, theSchemaVersion);
} }
@ -82,6 +87,20 @@ public class RenameColumnTask extends BaseTableTask {
throw new SQLException("Can not rename " + getTableName() + "." + myOldName + " to " + myNewName + " because both columns exist and data exists in " + myNewName); throw new SQLException("Can not rename " + getTableName() + "." + myOldName + " to " + myNewName + " because both columns exist and data exists in " + myNewName);
} }
if (getDriverType().equals(DriverTypeEnum.MYSQL_5_7) || mySimulateMySQLForTest) {
// Some DBs such as MYSQL require that foreign keys depending on the column be explicitly dropped before the column itself is dropped.
logInfo(ourLog, "Table {} has columns {} and {} - Going to drop any foreign keys depending on column {} before renaming", getTableName(), myOldName, myNewName, myNewName);
Set<String> foreignKeys = JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), myNewName, getTableName());
if(foreignKeys != null) {
for (String foreignKey:foreignKeys) {
List<String> dropFkSqls = DropForeignKeyTask.generateSql(getTableName(), foreignKey, getDriverType());
for(String dropFkSql : dropFkSqls) {
executeSql(getTableName(), dropFkSql);
}
}
}
}
logInfo(ourLog, "Table {} has columns {} and {} - Going to drop {} before renaming", getTableName(), myOldName, myNewName, myNewName); logInfo(ourLog, "Table {} has columns {} and {} - Going to drop {} before renaming", getTableName(), myOldName, myNewName, myNewName);
String sql = DropColumnTask.createSql(getTableName(), myNewName); String sql = DropColumnTask.createSql(getTableName(), myNewName);
executeSql(getTableName(), sql); executeSql(getTableName(), sql);
@ -124,7 +143,8 @@ public class RenameColumnTask extends BaseTableTask {
sql = "ALTER TABLE " + getTableName() + " CHANGE COLUMN " + myOldName + " TO " + myNewName; sql = "ALTER TABLE " + getTableName() + " CHANGE COLUMN " + myOldName + " TO " + myNewName;
break; break;
case MYSQL_5_7: case MYSQL_5_7:
sql = "ALTER TABLE " + getTableName() + " CHANGE COLUMN " + myOldName + " " + myNewName + " " + theExistingType + " " + theExistingNotNull; // Quote the column names as "SYSTEM" is a reserved word in MySQL
sql = "ALTER TABLE " + getTableName() + " CHANGE COLUMN `" + myOldName + "` `" + myNewName + "` " + theExistingType + " " + theExistingNotNull;
break; break;
case POSTGRES_9_4: case POSTGRES_9_4:
case ORACLE_12C: case ORACLE_12C:
@ -156,4 +176,9 @@ public class RenameColumnTask extends BaseTableTask {
theBuilder.append(myOldName); theBuilder.append(myOldName);
theBuilder.append(myNewName); theBuilder.append(myNewName);
} }
@VisibleForTesting
void setSimulateMySQLForTest(boolean theSimulateMySQLForTest) {
mySimulateMySQLForTest = theSimulateMySQLForTest;
}
} }

View File

@ -110,6 +110,9 @@ public class RenameIndexTask extends BaseTableTask {
// Drop constraint // Drop constraint
switch (theDriverType) { switch (theDriverType) {
case MYSQL_5_7: case MYSQL_5_7:
// Quote the index names as "PRIMARY" is a reserved word in MySQL
sql.add("rename index `" + theOldIndexName + "` to `" + theNewIndexName + "`");
break;
case MARIADB_10_1: case MARIADB_10_1:
case DERBY_EMBEDDED: case DERBY_EMBEDDED:
sql.add("rename index " + theOldIndexName + " to " + theNewIndexName); sql.add("rename index " + theOldIndexName + " to " + theNewIndexName);

View File

@ -118,7 +118,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
pkgVerRes.addColumn("RES_SIZE_BYTES").nonNullable().type(ColumnTypeEnum.LONG); pkgVerRes.addColumn("RES_SIZE_BYTES").nonNullable().type(ColumnTypeEnum.LONG);
pkgVerRes.addColumn("UPDATED_TIME").nonNullable().type(ColumnTypeEnum.DATE_TIMESTAMP); pkgVerRes.addColumn("UPDATED_TIME").nonNullable().type(ColumnTypeEnum.DATE_TIMESTAMP);
pkgVerRes.addForeignKey("20200610.11", "FK_NPM_PACKVERRES_PACKVER").toColumn("PACKVER_PID").references("NPM_PACKAGE_VER", "PID"); pkgVerRes.addForeignKey("20200610.11", "FK_NPM_PACKVERRES_PACKVER").toColumn("PACKVER_PID").references("NPM_PACKAGE_VER", "PID");
pkgVerRes.addForeignKey("20200610.12", "FK_NPM_PKVR_RESID").toColumn("BINARY_RES_ID").references("HFJ_RESOURCE", "PID"); pkgVerRes.addForeignKey("20200610.12", "FK_NPM_PKVR_RESID").toColumn("BINARY_RES_ID").references("HFJ_RESOURCE", "RES_ID");
pkgVerRes.addIndex("20200610.13", "IDX_PACKVERRES_URL").unique(false).withColumns("CANONICAL_URL"); pkgVerRes.addIndex("20200610.13", "IDX_PACKVERRES_URL").unique(false).withColumns("CANONICAL_URL");
} }

View File

@ -7,6 +7,8 @@ import java.sql.SQLException;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
public class DropColumnTest extends BaseTest { public class DropColumnTest extends BaseTest {
@ -34,4 +36,39 @@ public class DropColumnTest extends BaseTest {
} }
@Test
public void testDropForeignKeyColumn() throws SQLException {
executeSql("create table PARENT (PID bigint not null, TEXTCOL varchar(255), primary key (PID))");
executeSql("create table SIBLING (PID bigint not null, TEXTCOL varchar(255), primary key (PID))");
executeSql("create table CHILD (PID bigint not null, PARENTREF bigint, SIBLINGREF bigint)");
executeSql("alter table CHILD add constraint FK_MOM foreign key (PARENTREF) references PARENT(PID)");
executeSql("alter table CHILD add constraint FK_BROTHER foreign key (SIBLINGREF) references SIBLING(PID)");
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "SIBLING", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "PARENTREF", "CHILD"), containsInAnyOrder("FK_MOM"));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "SIBLINGREF", "CHILD"), containsInAnyOrder("FK_BROTHER"));
DropColumnTask task = new DropColumnTask("1", "1");
task.setTableName("CHILD");
task.setColumnName("PARENTREF");
getMigrator().addTask(task);
getMigrator().migrate();
assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "CHILD"), containsInAnyOrder("PID", "SIBLINGREF"));
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), empty());
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "SIBLING", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "PARENTREF", "CHILD"), empty());
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "SIBLINGREF", "CHILD"), containsInAnyOrder("FK_BROTHER"));
// Do it again to make sure there is no error
getMigrator().migrate();
getMigrator().migrate();
}
} }

View File

@ -8,7 +8,7 @@ public class RenameColumnTaskDbSpecificTest {
@Test @Test
public void testBuildSqlStatementForMySql() { public void testBuildSqlStatementForMySql() {
assertEquals("ALTER TABLE SOMETABLE CHANGE COLUMN myTextCol TEXTCOL integer null", createRenameColumnSql(DriverTypeEnum.MYSQL_5_7)); assertEquals("ALTER TABLE SOMETABLE CHANGE COLUMN `myTextCol` `TEXTCOL` integer null", createRenameColumnSql(DriverTypeEnum.MYSQL_5_7));
} }
private String createRenameColumnSql(DriverTypeEnum theDriverTypeEnum) { private String createRenameColumnSql(DriverTypeEnum theDriverTypeEnum) {

View File

@ -9,6 +9,8 @@ import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -35,6 +37,41 @@ public class RenameColumnTaskTest extends BaseTest {
assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("PID", "TEXTCOL")); assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("PID", "TEXTCOL"));
} }
@Test
public void testForeignKeyColumnAlreadyExists_MySql() throws SQLException {
testForeignKeyColumnAlreadyExists(true);
}
private void testForeignKeyColumnAlreadyExists(boolean isMySql) throws SQLException {
executeSql("create table PARENT (PID bigint not null, TEXTCOL varchar(255), primary key (PID))");
executeSql("create table CHILD (PID bigint not null, PARENTREF bigint)");
executeSql("alter table CHILD add constraint FK_MOM foreign key (PARENTREF) references PARENT(PID)");
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "PARENTREF", "CHILD"), containsInAnyOrder("FK_MOM"));
RenameColumnTask task = new RenameColumnTask("1", "1");
task.setTableName("CHILD");
task.setOldName("myParentRef");
task.setNewName("PARENTREF");
task.setSimulateMySQLForTest(isMySql);
getMigrator().addTask(task);
getMigrator().migrate();
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "CHILD"), containsInAnyOrder("PID", "PARENTREF"));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "PARENTREF", "CHILD"), containsInAnyOrder("FK_MOM"));
}
@Test
public void testForeignKeyColumnAlreadyExists_OtherDB() throws SQLException {
testForeignKeyColumnAlreadyExists(false);
}
@Test @Test
public void testBothExistDeleteTargetFirst() throws SQLException { public void testBothExistDeleteTargetFirst() throws SQLException {
executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255), myTextCol varchar(255))"); executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255), myTextCol varchar(255))");
@ -54,7 +91,49 @@ public class RenameColumnTaskTest extends BaseTest {
} }
@Test @Test
public void testBothExistDeleteTargetFirstDataExistsInSourceAndTarget() throws SQLException { public void testForeignKeyColumnBothExistDeleteTargetFirst_MySql() throws SQLException {
testForeignKeyColumnBothExistDeleteTargetFirst(true);
}
private void testForeignKeyColumnBothExistDeleteTargetFirst(boolean isMySql) throws SQLException {
executeSql("create table PARENT (PARENTID bigint not null, TEXTCOL varchar(255), primary key (PARENTID))");
executeSql("create table RELATION (RELATIONID bigint not null, TEXTCOL varchar(255), primary key (RELATIONID))");
executeSql("create table CHILD (PID bigint not null, PARENTREF bigint, NOKREF bigint)");
executeSql("alter table CHILD add constraint FK_MOM foreign key (PARENTREF) references PARENT(PARENTID)");
executeSql("alter table CHILD add constraint FK_NOK foreign key (NOKREF) references RELATION(RELATIONID)");
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "RELATION", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "PARENTREF", "CHILD"), containsInAnyOrder("FK_MOM"));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "NOKREF", "CHILD"), containsInAnyOrder("FK_NOK"));
RenameColumnTask task = new RenameColumnTask("1", "1");
task.setTableName("CHILD");
task.setOldName("PARENTREF");
task.setNewName("NOKREF");
task.setDeleteTargetColumnFirstIfBothExist(true);
task.setSimulateMySQLForTest(isMySql);
getMigrator().addTask(task);
getMigrator().migrate();
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "RELATION", "CHILD"), empty());
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "CHILD"), containsInAnyOrder("PID", "NOKREF"));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "NOKREF", "CHILD"), containsInAnyOrder("FK_MOM"));
}
@Test
public void testForeignKeyColumnBothExistDeleteTargetFirst_OtherDB() throws SQLException {
testForeignKeyColumnBothExistDeleteTargetFirst(false);
}
@Test
public void testBothExistDeleteTargetFirstDataExistsInSourceAndTarget() {
executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255), myTextCol varchar(255))"); executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255), myTextCol varchar(255))");
executeSql("INSERT INTO SOMETABLE (PID, TEXTCOL, myTextCol) VALUES (123, 'AAA', 'BBB')"); executeSql("INSERT INTO SOMETABLE (PID, TEXTCOL, myTextCol) VALUES (123, 'AAA', 'BBB')");
@ -91,6 +170,42 @@ public class RenameColumnTaskTest extends BaseTest {
assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("PID", "TEXTCOL")); assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("PID", "TEXTCOL"));
} }
@Test
public void testForeignKeyColumnDoesntAlreadyExist_MySql() throws SQLException {
testForeignKeyColumnDoesntAlreadyExist(true);
}
private void testForeignKeyColumnDoesntAlreadyExist(boolean isMySql) throws SQLException {
executeSql("create table PARENT (PARENTID bigint not null, TEXTCOL varchar(255), primary key (PARENTID))");
executeSql("create table CHILD (PID bigint not null, PARENTREF bigint)");
executeSql("alter table CHILD add constraint FK_MOM foreign key (PARENTREF) references PARENT(PARENTID)");
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "PARENTREF", "CHILD"), containsInAnyOrder("FK_MOM"));
RenameColumnTask task = new RenameColumnTask("1", "1");
task.setTableName("CHILD");
task.setOldName("PARENTREF");
task.setNewName("MOMREF");
task.setSimulateMySQLForTest(isMySql);
getMigrator().addTask(task);
getMigrator().migrate();
assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1));
assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "CHILD"), containsInAnyOrder("PID", "MOMREF"));
assertThat(JdbcUtils.getForeignKeysForColumn(getConnectionProperties(), "MOMREF", "CHILD"), containsInAnyOrder("FK_MOM"));
}
@Test
public void testForeignKeyColumnDoesntAlreadyExist_OtherDB() throws SQLException {
testForeignKeyColumnDoesntAlreadyExist(false);
}
@Test @Test
public void testNeitherColumnExists() { public void testNeitherColumnExists() {
executeSql("create table SOMETABLE (PID bigint not null)"); executeSql("create table SOMETABLE (PID bigint not null)");

View File

@ -42,7 +42,7 @@ public class ResourceTag extends BaseTag {
@Column(name = "PID") @Column(name = "PID")
private Long myId; private Long myId;
@ManyToOne(cascade = {}) @ManyToOne(cascade = {}, fetch = FetchType.LAZY)
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_RESTAG_RESOURCE")) @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_RESTAG_RESOURCE"))
private ResourceTable myResource; private ResourceTable myResource;

View File

@ -159,8 +159,9 @@ public class SearchParameterMap implements Serializable {
} }
} }
public void addRevInclude(Include theInclude) { public SearchParameterMap addRevInclude(Include theInclude) {
getRevIncludes().add(theInclude); getRevIncludes().add(theInclude);
return this;
} }
private void addUrlIncludeParams(StringBuilder b, String paramName, Set<Include> theList) { private void addUrlIncludeParams(StringBuilder b, String paramName, Set<Include> theList) {
@ -268,8 +269,9 @@ public class SearchParameterMap implements Serializable {
return mySort; return mySort;
} }
public void setSort(SortSpec theSort) { public SearchParameterMap setSort(SortSpec theSort) {
mySort = theSort; mySort = theSort;
return this;
} }
/** /**

View File

@ -6,9 +6,30 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.annotation.Block; import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Enumeration;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.MarkdownType;
import org.hl7.fhir.r4.model.Money;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Patient.LinkType; import org.hl7.fhir.r4.model.Patient.LinkType;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.PrimitiveType;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.SimpleQuantity;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -18,11 +39,22 @@ import org.slf4j.LoggerFactory;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -31,6 +63,15 @@ public class FhirTerserR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirTerserR4Test.class); private static final Logger ourLog = LoggerFactory.getLogger(FhirTerserR4Test.class);
private static FhirContext ourCtx = FhirContext.forR4(); private static FhirContext ourCtx = FhirContext.forR4();
@Test
public void testGetValuesCreateEnumeration_SetsEnumFactory() {
Patient patient = new Patient();
Enumeration<?> enumeration = (Enumeration<?>) ourCtx.newTerser().getValues(patient, "Patient.gender", Enumeration.class, true).get(0);
assertNotNull(enumeration.getEnumFactory());
}
@Test @Test
public void testClear() { public void testClear() {
Bundle input = new Bundle(); Bundle input = new Bundle();

View File

@ -70,7 +70,7 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport {
@Override @Override
public IValidationSupport.ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { public IValidationSupport.ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) {
return myWrap.expandValueSet(theValidationSupportContext, null, theValueSetToExpand); return myWrap.expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand);
} }
@Override @Override

View File

@ -2,13 +2,17 @@ package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.ConceptValidationOptions;
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.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
@ -42,6 +46,64 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null); return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null);
} }
@Override
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
IBaseResource valueSet = theValueSet;
String valueSetUrl = DefaultProfileValidationSupport.getConformanceResourceUrl(myCtx, valueSet);
if (isNotBlank(valueSetUrl)) {
valueSet = null;
} else {
valueSetUrl = null;
}
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, valueSetUrl, valueSet);
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
IGenericClient client = provideClient();
Class<? extends IBaseBundle> bundleType = myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class);
IBaseBundle results = client
.search()
.forResource("CodeSystem")
.where(CodeSystem.URL.matches().value(theSystem))
.returnBundle(bundleType)
.execute();
List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results);
if (resultsList.size() > 0) {
return resultsList.get(0);
}
return null;
}
@Override
public IBaseResource fetchValueSet(String theValueSetUrl) {
IGenericClient client = provideClient();
Class<? extends IBaseBundle> bundleType = myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class);
IBaseBundle results = client
.search()
.forResource("ValueSet")
.where(CodeSystem.URL.matches().value(theValueSetUrl))
.returnBundle(bundleType)
.execute();
List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results);
if (resultsList.size() > 0) {
return resultsList.get(0);
}
return null;
}
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
return fetchCodeSystem(theSystem) != null;
}
@Override
public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
return fetchValueSet(theValueSetUrl) != null;
}
private IGenericClient provideClient() { private IGenericClient provideClient() {
IGenericClient retVal = myCtx.newRestfulGenericClient(myBaseUrl); IGenericClient retVal = myCtx.newRestfulGenericClient(myBaseUrl);
for (Object next : myClientInterceptors) { for (Object next : myClientInterceptors) {
@ -50,11 +112,6 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return retVal; return retVal;
} }
@Override
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, null, theValueSet);
}
protected CodeValidationResult invokeRemoteValidateCode(String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) { protected CodeValidationResult invokeRemoteValidateCode(String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) {
if (isBlank(theCode)) { if (isBlank(theCode)) {
return null; return null;
@ -64,23 +121,38 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
IBaseParameters input = ParametersUtil.newInstance(getFhirContext()); IBaseParameters input = ParametersUtil.newInstance(getFhirContext());
if (isNotBlank(theValueSetUrl)) { String resourceType = "ValueSet";
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "url", theValueSetUrl); if (theValueSet == null && theValueSetUrl == null) {
} resourceType = "CodeSystem";
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "code", theCode);
if (isNotBlank(theCodeSystem)) { ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "url", theCodeSystem);
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "system", theCodeSystem); ParametersUtil.addParameterToParametersString(getFhirContext(), input, "code", theCode);
} if (isNotBlank(theDisplay)) {
if (isNotBlank(theDisplay)) { ParametersUtil.addParameterToParametersString(getFhirContext(), input, "display", theDisplay);
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "display", theDisplay); }
}
if (theValueSet != null) { } else {
ParametersUtil.addParameterToParameters(getFhirContext(), input, "valueSet", theValueSet);
if (isNotBlank(theValueSetUrl)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "url", theValueSetUrl);
}
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "code", theCode);
if (isNotBlank(theCodeSystem)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "system", theCodeSystem);
}
if (isNotBlank(theDisplay)) {
ParametersUtil.addParameterToParametersString(getFhirContext(), input, "display", theDisplay);
}
if (theValueSet != null) {
ParametersUtil.addParameterToParameters(getFhirContext(), input, "valueSet", theValueSet);
}
} }
IBaseParameters output = client IBaseParameters output = client
.operation() .operation()
.onType("ValueSet") .onType(resourceType)
.named("validate-code") .named("validate-code")
.withParameters(input) .withParameters(input)
.execute(); .execute();

View File

@ -122,11 +122,19 @@ public class ValidationSupportChain implements IValidationSupport {
myChain.add(theIndex, theValidationSupport); myChain.add(theIndex, theValidationSupport);
} }
/**
* Removes an item from the chain. Note that this method is mostly intended for testing. Removing items from the chain while validation is
* actually occurring is not an expected use case for this class.
*/
public void removeValidationSupport(IValidationSupport theValidationSupport) {
myChain.remove(theValidationSupport);
}
@Override @Override
public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {
// TODO: test if code system is supported? // TODO: test if code system is supported?
ValueSetExpansionOutcome expanded = next.expandValueSet(theValidationSupportContext, null, theValueSetToExpand); ValueSetExpansionOutcome expanded = next.expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand);
if (expanded != null) { if (expanded != null) {
return expanded; return expanded;
} }

View File

@ -21,6 +21,7 @@ import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
import javax.annotation.Nonnull;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -197,6 +198,21 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
@Override @Override
protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) { protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext();
return new ValidatorWrapper()
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
.setValidatorResourceFetcher(getValidatorResourceFetcher())
.setAssumeValidRestReferences(isAssumeValidRestReferences())
.validate(wrappedWorkerContext, theValidationCtx);
}
@Nonnull
protected VersionSpecificWorkerContextWrapper provideWorkerContext() {
VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
if (wrappedWorkerContext == null) { if (wrappedWorkerContext == null) {
VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter; VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter;
@ -213,7 +229,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical; org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical;
if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) { if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) {
if (!valueSet.hasCompose()) { if (!valueSet.hasCompose()) {
org.hl7.fhir.r5.model.ValueSet valueSetR5 = (org.hl7.fhir.r5.model.ValueSet) retVal; ValueSet valueSetR5 = (ValueSet) retVal;
valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem()); valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem());
} }
} }
@ -257,16 +273,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter); wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter);
} }
myWrappedWorkerContext = wrappedWorkerContext; myWrappedWorkerContext = wrappedWorkerContext;
return wrappedWorkerContext;
return new ValidatorWrapper()
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
.setValidatorResourceFetcher(getValidatorResourceFetcher())
.setAssumeValidRestReferences(isAssumeValidRestReferences())
.validate(wrappedWorkerContext, theValidationCtx);
} }
private FhirContext getDstu2Context() { private FhirContext getDstu2Context() {
@ -309,7 +316,9 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
*/ */
public void invalidateCaches() { public void invalidateCaches() {
myValidationSupport.invalidateCaches(); myValidationSupport.invalidateCaches();
myWrappedWorkerContext.invalidateCaches(); if (myWrappedWorkerContext != null) {
myWrappedWorkerContext.invalidateCaches();
}
} }

View File

@ -5,9 +5,15 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.annotation.IdParam; 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.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerRule; import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType; 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.CodeType;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
@ -21,6 +27,9 @@ import org.junit.Test;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -37,13 +46,18 @@ public class RemoteTerminologyServiceValidationSupportTest {
@Rule @Rule
public RestfulServerRule myRestfulServerRule = new RestfulServerRule(ourCtx); public RestfulServerRule myRestfulServerRule = new RestfulServerRule(ourCtx);
private MyMockTerminologyServiceProvider myProvider; private MyValueSetProvider myValueSetProvider;
private RemoteTerminologyServiceValidationSupport mySvc; private RemoteTerminologyServiceValidationSupport mySvc;
private MyCodeSystemProvider myCodeSystemProvider;
@Before @Before
public void before() { public void before() {
myProvider = new MyMockTerminologyServiceProvider(); myValueSetProvider = new MyValueSetProvider();
myRestfulServerRule.getRestfulServer().registerProvider(myProvider); myRestfulServerRule.getRestfulServer().registerProvider(myValueSetProvider);
myCodeSystemProvider = new MyCodeSystemProvider();
myRestfulServerRule.getRestfulServer().registerProvider(myCodeSystemProvider);
String baseUrl = "http://localhost:" + myRestfulServerRule.getPort(); String baseUrl = "http://localhost:" + myRestfulServerRule.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx);
@ -53,7 +67,7 @@ public class RemoteTerminologyServiceValidationSupportTest {
@After @After
public void after() { public void after() {
assertThat(myProvider.myInvocationCount, lessThan(2)); assertThat(myValueSetProvider.myInvocationCount, lessThan(2));
} }
@Test @Test
@ -64,7 +78,7 @@ public class RemoteTerminologyServiceValidationSupportTest {
@Test @Test
public void testValidateCode_SystemCodeDisplayUrl_Success() { public void testValidateCode_SystemCodeDisplayUrl_Success() {
createNextReturnParameters(true, DISPLAY, null); createNextValueSetReturnParameters(true, DISPLAY, null);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL); IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertEquals(CODE, outcome.getCode()); assertEquals(CODE, outcome.getCode());
@ -72,16 +86,16 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(null, outcome.getSeverity()); assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage()); assertEquals(null, outcome.getMessage());
assertEquals(CODE, myProvider.myLastCode.getCode()); assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myProvider.myLastDisplay.getValue()); assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue()); assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myProvider.myLastUrl.getValue()); assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue());
assertEquals(null, myProvider.myLastValueSet); assertEquals(null, myValueSetProvider.myLastValueSet);
} }
@Test @Test
public void testValidateCode_SystemCodeDisplayUrl_Error() { public void testValidateCode_SystemCodeDisplayUrl_Error() {
createNextReturnParameters(false, null, ERROR_MESSAGE); createNextValueSetReturnParameters(false, null, ERROR_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL); IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertEquals(null, outcome.getCode()); assertEquals(null, outcome.getCode());
@ -89,16 +103,32 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity()); assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
assertEquals(ERROR_MESSAGE, outcome.getMessage()); assertEquals(ERROR_MESSAGE, outcome.getMessage());
assertEquals(CODE, myProvider.myLastCode.getCode()); assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myProvider.myLastDisplay.getValue()); assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue()); assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myProvider.myLastUrl.getValue()); assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue());
assertEquals(null, myProvider.myLastValueSet); assertEquals(null, myValueSetProvider.myLastValueSet);
} }
@Test
public void testValidateCodeInCodeSystem_Good() {
createNextCodeSystemReturnParameters(true, DISPLAY, null);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(DISPLAY, myCodeSystemProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
}
@Test @Test
public void testValidateCodeInValueSet_SystemCodeDisplayVS_Good() { public void testValidateCodeInValueSet_SystemCodeDisplayVS_Good() {
createNextReturnParameters(true, DISPLAY, null); createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet(); ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL); valueSet.setUrl(VALUE_SET_URL);
@ -109,34 +139,127 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(null, outcome.getSeverity()); assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage()); assertEquals(null, outcome.getMessage());
assertEquals(CODE, myProvider.myLastCode.getCode()); assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myProvider.myLastDisplay.getValue()); assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue()); assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(null, myProvider.myLastUrl); assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValueAsString());
assertEquals(VALUE_SET_URL, myProvider.myLastValueSet.getUrl()); assertEquals(null, myValueSetProvider.myLastValueSet);
} }
public void createNextReturnParameters(boolean theResult, String theDisplay, String theMessage) { @Test
myProvider.myNextReturn = new Parameters(); public void testIsValueSetSupported_False() {
myProvider.myNextReturn.addParameter("result", theResult); myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
myProvider.myNextReturn.addParameter("display", theDisplay);
boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS");
assertEquals(false, outcome);
assertEquals("http://loinc.org/VS", myValueSetProvider.myLastUrlParam.getValue());
}
@Test
public void testIsValueSetSupported_True() {
myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/123"));
boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS");
assertEquals(true, outcome);
assertEquals("http://loinc.org/VS", myValueSetProvider.myLastUrlParam.getValue());
}
@Test
public void testIsCodeSystemSupported_False() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org");
assertEquals(false, outcome);
assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue());
}
@Test
public void testIsCodeSystemSupported_True() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/123"));
boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org");
assertEquals(true, outcome);
assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue());
}
private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myCodeSystemProvider.myNextReturnParams = new Parameters();
myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult);
myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) { if (theMessage != null) {
myProvider.myNextReturn.addParameter("message", theMessage); myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage);
} }
} }
private static class MyMockTerminologyServiceProvider { private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myValueSetProvider.myNextReturnParams = new Parameters();
myValueSetProvider.myNextReturnParams.addParameter("result", theResult);
myValueSetProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myValueSetProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private static class MyCodeSystemProvider implements IResourceProvider {
private UriParam myLastUrlParam;
private List<CodeSystem> myNextReturnCodeSystems;
private int myInvocationCount;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Parameters myNextReturnParams;
@Operation(name = "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 = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
myInvocationCount++;
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Search
public List<CodeSystem> find(@RequiredParam(name="url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnCodeSystems != null;
return myNextReturnCodeSystems;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
}
private Parameters myNextReturn; private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private List<ValueSet> myNextReturnValueSets;
private UriType myLastUrl; private UriType myLastUrl;
private CodeType myLastCode; private CodeType myLastCode;
private int myInvocationCount; private int myInvocationCount;
private UriType myLastSystem; private UriType myLastSystem;
private StringType myLastDisplay; private StringType myLastDisplay;
private ValueSet myLastValueSet; private ValueSet myLastValueSet;
private UriParam myLastUrlParam;
@Operation(name = "validate-code", idempotent = true, typeName = "ValueSet", returnParameters = { @Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1), @OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class), @OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class) @OperationParam(name = "display", type = StringType.class)
@ -156,11 +279,22 @@ public class RemoteTerminologyServiceValidationSupportTest {
myLastSystem = theSystem; myLastSystem = theSystem;
myLastDisplay = theDisplay; myLastDisplay = theDisplay;
myLastValueSet = theValueSet; myLastValueSet = theValueSet;
return myNextReturn; return myNextReturnParams;
} }
@Search
public List<ValueSet> find(@RequiredParam(name="url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnValueSets != null;
return myNextReturnValueSets;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
} }
} }

View File

@ -693,7 +693,7 @@
<!--<derby_version>10.15.1.3</derby_version>--> <!--<derby_version>10.15.1.3</derby_version>-->
<error_prone_annotations_version>2.3.4</error_prone_annotations_version> <error_prone_annotations_version>2.3.4</error_prone_annotations_version>
<error_prone_core_version>2.3.3</error_prone_core_version> <error_prone_core_version>2.3.3</error_prone_core_version>
<guava_version>28.2-jre</guava_version> <guava_version>29.0-jre</guava_version>
<gson_version>2.8.5</gson_version> <gson_version>2.8.5</gson_version>
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version> <jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
<jaxb_api_version>2.3.1</jaxb_api_version> <jaxb_api_version>2.3.1</jaxb_api_version>
@ -1670,7 +1670,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId> <artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version> <version>3.0.0</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>