Merge remote-tracking branch 'remotes/origin/master' into batch-empi-job

This commit is contained in:
Tadgh 2020-07-29 09:42:12 -07:00
commit 0e88f359f5
10 changed files with 344 additions and 31 deletions

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2005
title: When generating a snapshot for a StructureDefinition that extends another non-base StructureDefinition, if the parent
SD did not have a snapshot itself, the child snapshot would be empty. This has been corrected.

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 2006
title: "When updating a resource with links that change, to reduce database operations, hapi-fhir reuses link index records.
However, all the columns were being properly updated except for the source path column which was accidentally missed and
continued to hold the previous value. This resulted in mismatched source paths and values. This has been corrected."

View File

@ -19,19 +19,75 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.HasOrListParam;
import ca.uhn.fhir.rest.param.HasParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Appointment;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.Device;
import org.hl7.fhir.dstu3.model.DiagnosticReport;
import org.hl7.fhir.dstu3.model.Encounter;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Group;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Immunization;
import org.hl7.fhir.dstu3.model.ImmunizationRecommendation;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Location;
import org.hl7.fhir.dstu3.model.Medication;
import org.hl7.fhir.dstu3.model.MedicationAdministration;
import org.hl7.fhir.dstu3.model.MedicationRequest;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Period;
import org.hl7.fhir.dstu3.model.Practitioner;
import org.hl7.fhir.dstu3.model.ProcedureRequest;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.Range;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.SimpleQuantity;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.dstu3.model.Substance;
import org.hl7.fhir.dstu3.model.Task;
import org.hl7.fhir.dstu3.model.Timing;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -63,6 +119,7 @@ import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -3622,6 +3679,37 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
assertThat(ids.toString(), ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
}
@Test
public void testReplaceLinkSearchIndex() {
Patient pt = new Patient();
IIdType ptId = myPatientDao.create(pt).getId().toVersionless();
Observation obs = new Observation();
obs.setSubject(new Reference(ptId));
IIdType obsId = myObservationDao.create(obs).getId().toVersionless();
Practitioner pr = new Practitioner();
IIdType prId = myPractitionerDao.create(pr).getId().toVersionless();
obs.setId(obsId);
obs.setSubject(null);
obs.addPerformer(new Reference(prId));
myCaptureQueriesListener.clear();
myObservationDao.update(obs);
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
String unformattedSql = myCaptureQueriesListener.getUpdateQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder(
"SRC_PATH='Observation.performer'",
"SRC_RESOURCE_ID='" + obsId.getIdPart() + "'",
"TARGET_RESOURCE_ID='" + prId.getIdPart() + "'",
"TARGET_RESOURCE_TYPE='Practitioner'"
));
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
}
private String toStringMultiline(List<?> theResults) {
StringBuilder b = new StringBuilder();
for (Object next : theResults) {

View File

@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SuppressWarnings({"unchecked", "deprecation"})
public class FhirResourceDaoR4StructureDefinitionTest extends BaseJpaR4Test {
@ -47,5 +48,24 @@ public class FhirResourceDaoR4StructureDefinitionTest extends BaseJpaR4Test {
}
/**
* Make sure that if one SD extends another SD, and the parent SD hasn't been snapshotted itself, the child can
* be snapshotted.
*/
@Test
public void testGenerateSnapshotChained() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/StructureDefinition-kfdrc-patient.json");
myStructureDefinitionDao.update(sd);
StructureDefinition sd2 = loadResourceFromClasspath(StructureDefinition.class, "/r4/StructureDefinition-kfdrc-patient-no-phi.json");
myStructureDefinitionDao.update(sd2);
StructureDefinition snapshotted = myStructureDefinitionDao.generateSnapshot(sd2, null, null, null);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(snapshotted));
assertTrue(snapshotted.getSnapshot().getElement().size() > 0);
}
}

View File

@ -0,0 +1,62 @@
{
"resourceType": "StructureDefinition",
"id": "kfdrc-patient-no-phi",
"url": "http://fhir.kids-first.io/StructureDefinition/kfdrc-patient-no-phi",
"version": "0.1.0",
"name": "kfdrc-patient-no-phi",
"title": "Kids First DRC Patient without Protected Health Information",
"status": "draft",
"fhirVersion": "4.0.0",
"kind": "resource",
"abstract": false,
"type": "Patient",
"baseDefinition": "http://fhir.kids-first.io/StructureDefinition/kfdrc-patient",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Patient",
"path": "Patient"
},
{
"id": "Patient.name",
"path": "Patient.name",
"max": "0"
},
{
"id": "Patient.telecom",
"path": "Patient.telecom",
"max": "0"
},
{
"id": "Patient.birthDate",
"path": "Patient.birthDate",
"max": "0"
},
{
"id": "Patient.deceased[x]",
"path": "Patient.deceased[x]",
"type" : [
{
"code" : "boolean"
}
]
},
{
"id": "Patient.address",
"path": "Patient.address",
"max": "0"
},
{
"id": "Patient.photo",
"path": "Patient.photo",
"max": "0"
},
{
"id": "Patient.contact",
"path": "Patient.contact",
"max": "0"
}
]
}
}

View File

@ -0,0 +1,100 @@
{
"resourceType": "StructureDefinition",
"id": "kfdrc-patient",
"url": "http://fhir.kids-first.io/StructureDefinition/kfdrc-patient",
"version": "0.1.0",
"name": "kfdrc-patient",
"title": "Kids First DRC Patient",
"status": "draft",
"fhirVersion": "4.0.0",
"kind": "resource",
"abstract": false,
"type": "Patient",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Patient",
"path": "Patient"
},
{
"id": "Patient.extension",
"path": "Patient.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"ordered": false,
"rules": "open"
}
},
{
"id": "Patient.extension:us-core-ethnicity",
"path": "Patient.extension",
"sliceName": "us-core-ethnicity",
"sliceIsConstraining": false,
"short": "US Core Ethnicity Extension",
"definition": "Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.",
"min": 0,
"max": "1",
"type": [
{
"code": "Extension",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"
]
}
],
"mustSupport": true,
"isModifier": false
},
{
"id": "Patient.extension:us-core-race",
"path": "Patient.extension",
"sliceName": "us-core-race",
"short": "US Core Race Extension",
"definition": "Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.",
"min": 0,
"max": "1",
"type": [
{
"code": "Extension",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
]
}
],
"mustSupport": true,
"isModifier": false
},
{
"id": "Patient.extension:species",
"path": "Patient.extension",
"sliceName": "species",
"short": "Species",
"definition": "Species of the KF DRC Patient.",
"min": 0,
"max": "1",
"type": [
{
"code": "Extension",
"profile": [
"http://fhir.kids-first.io/StructureDefinition/species"
]
}
],
"mustSupport": true,
"isModifier": false,
"binding": {
"strength": "required",
"description": "Species of the Participant.",
"valueSet": "http://fhir.kids-first.io/ValueSet/species"
}
}
]
}
}

View File

@ -74,35 +74,21 @@ public class DropIndexTask extends BaseTableTask {
* constraint, and delete that constraint.
*/
@Language("SQL") String findConstraintSql;
@Language("SQL") String dropConstraintSql;
if (getDriverType() == DriverTypeEnum.H2_EMBEDDED) {
findConstraintSql = "SELECT DISTINCT INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE constraint_name = ? AND table_name = ?";
dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT " + myIndexName;
@Language("SQL") String findConstraintSql = "SELECT DISTINCT constraint_name FROM INFORMATION_SCHEMA.INDEXES WHERE constraint_name = ? AND table_name = ?";
@Language("SQL") String dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT ?";
findAndDropConstraint(findConstraintSql, dropConstraintSql);
} else if (getDriverType() == DriverTypeEnum.DERBY_EMBEDDED) {
findConstraintSql = "SELECT c.constraintname FROM sys.sysconstraints c, sys.systables t WHERE c.tableid = t.tableid AND c.constraintname = ? AND t.tablename = ?";
dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT " + myIndexName;
} else {
findConstraintSql = null;
dropConstraintSql = null;
}
@Language("SQL") String findConstraintSql = "SELECT c.constraintname FROM sys.sysconstraints c, sys.systables t WHERE c.tableid = t.tableid AND c.constraintname = ? AND t.tablename = ?";
@Language("SQL") String dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT ?";
findAndDropConstraint(findConstraintSql, dropConstraintSql);
} else if (getDriverType() == DriverTypeEnum.ORACLE_12C) {
@Language("SQL") String findConstraintSql = "SELECT DISTINCT constraint_name FROM user_cons_columns WHERE constraint_name = ? AND table_name = ?";
@Language("SQL") String dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT ?";
findAndDropConstraint(findConstraintSql, dropConstraintSql);
if (findConstraintSql != null) {
DataSource dataSource = Objects.requireNonNull(getConnectionProperties().getDataSource());
Boolean handled = getConnectionProperties().getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
RowMapperResultSetExtractor<String> resultSetExtractor = new RowMapperResultSetExtractor<>(new SingleColumnRowMapper<>(String.class));
List<String> outcome = jdbcTemplate.query(findConstraintSql, new Object[]{myIndexName, getTableName()}, resultSetExtractor);
assert outcome != null;
if (outcome.size() > 0) {
executeSql(getTableName(), dropConstraintSql);
return true;
}
return false;
});
if (Boolean.TRUE.equals(handled)) {
return;
}
findConstraintSql = "SELECT DISTINCT constraint_name FROM all_constraints WHERE index_name = ? AND table_name = ?";
findAndDropConstraint(findConstraintSql, dropConstraintSql);
}
Set<String> indexNames = JdbcUtils.getIndexNames(getConnectionProperties(), getTableName());
@ -124,6 +110,20 @@ public class DropIndexTask extends BaseTableTask {
}
}
public void findAndDropConstraint(String theFindConstraintSql, String theDropConstraintSql) {
DataSource dataSource = Objects.requireNonNull(getConnectionProperties().getDataSource());
getConnectionProperties().getTxTemplate().executeWithoutResult(t -> {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
RowMapperResultSetExtractor<String> resultSetExtractor = new RowMapperResultSetExtractor<>(new SingleColumnRowMapper<>(String.class));
List<String> outcome = jdbcTemplate.query(theFindConstraintSql, new Object[]{myIndexName, getTableName()}, resultSetExtractor);
assert outcome != null;
for (String next : outcome) {
String sql = theDropConstraintSql.replace("?", next);
executeSql(getTableName(), sql);
}
});
}
public DropIndexTask setIndexName(String theIndexName) {
myIndexName = theIndexName;
return this;
@ -166,9 +166,11 @@ public class DropIndexTask extends BaseTableTask {
sql.add("drop index " + theIndexName);
break;
case DERBY_EMBEDDED:
case ORACLE_12C:
sql.add("alter table " + theTableName + " drop constraint " + theIndexName);
break;
case ORACLE_12C:
sql.add("drop index " + theIndexName);
break;
case MSSQL_2012:
sql.add("drop index " + theIndexName + " on " + theTableName);
break;

View File

@ -181,10 +181,17 @@ public class Builder {
return task;
}
/**
* @deprecated Do not rename indexes - It is too hard to figure out what happened if something goes wrong
*/
@Deprecated
public void renameIndex(String theVersion, String theOldIndexName, String theNewIndexName) {
renameIndexOptional(false, theVersion, theOldIndexName, theNewIndexName);
}
/**
* @deprecated Do not rename indexes - It is too hard to figure out what happened if something goes wrong
*/
public void renameIndexStub(String theVersion, String theOldIndexName, String theNewIndexName) {
renameIndexOptional(true, theVersion, theOldIndexName, theNewIndexName);
}

View File

@ -26,7 +26,21 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.search.annotations.Field;
import org.hl7.fhir.instance.model.api.IIdType;
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.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import java.util.Date;
@Entity
@ -118,6 +132,7 @@ public class ResourceLink extends BaseResourceIndex {
@Override
public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
ResourceLink source = (ResourceLink) theSource;
mySourcePath = source.getSourcePath();
myTargetResource = source.getTargetResource();
myTargetResourceId = source.getTargetResourceId();
myTargetResourcePid = source.getTargetResourcePid();

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContex
import org.hl7.fhir.common.hapi.validation.validator.VersionTypeConverterDstu3;
import org.hl7.fhir.common.hapi.validation.validator.VersionTypeConverterR4;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.slf4j.Logger;
@ -90,10 +91,17 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
org.hl7.fhir.r5.model.StructureDefinition baseCanonical = (org.hl7.fhir.r5.model.StructureDefinition) converter.toCanonical(base);
if (baseCanonical.getSnapshot().getElement().isEmpty()) {
// If the base definition also doesn't have a snapshot, generate that first
theValidationSupportContext.getRootValidationSupport().generateSnapshot(theValidationSupportContext, base, null, null, null);
baseCanonical = (org.hl7.fhir.r5.model.StructureDefinition) converter.toCanonical(base);
}
ArrayList<ValidationMessage> messages = new ArrayList<>();
org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new ProfileKnowledgeWorkerR5(myCtx);
IWorkerContext context = new VersionSpecificWorkerContextWrapper(theValidationSupportContext, converter);
new org.hl7.fhir.r5.conformance.ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(baseCanonical, inputCanonical, theUrl, theWebUrl, theProfileName);
ProfileUtilities profileUtilities = new ProfileUtilities(context, messages, profileKnowledgeProvider);
profileUtilities.generateSnapshot(baseCanonical, inputCanonical, theUrl, theWebUrl, theProfileName);
switch (theInput.getStructureFhirVersionEnum()) {
case DSTU3: