This commit is contained in:
Tadgh 2023-05-06 10:28:25 -07:00
parent df8d2cd797
commit abbb191928
12 changed files with 272 additions and 136 deletions

View File

@ -1,5 +1,3 @@
package ca.uhn.fhir.model.api;
/*
* #%L
* HAPI FHIR - Core Library
@ -19,6 +17,7 @@ package ca.uhn.fhir.model.api;
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.model.api;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -26,6 +25,7 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import java.net.URI;
import java.util.Objects;
/**
* A single tag
@ -59,7 +59,7 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
private String myScheme;
private String myTerm;
private String myVersion;
private boolean myUserSelected;
private Boolean myUserSelected;
public Tag() {
}
@ -114,40 +114,23 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
return false;
if (getClass() != obj.getClass())
return false;
Tag other = (Tag) obj;
if (myScheme == null) {
if (other.myScheme != null)
return false;
} else if (!myScheme.equals(other.myScheme))
return false;
if (myTerm == null) {
if (other.myTerm != null)
return false;
} else if (!myTerm.equals(other.myTerm))
return false;
if (myVersion == null) {
if (other.getVersion() != null)
return false;
} else if (!myVersion.equals(other.getVersion()))
return false;
if (myUserSelected != other.getUserSelected())
return false;
return true;
return
Objects.equals(myScheme, other.myScheme) &&
Objects.equals(myTerm, other.myTerm) &&
Objects.equals(myVersion, other.myVersion) &&
Objects.equals(myUserSelected, other.myUserSelected);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((myScheme == null) ? 0 : myScheme.hashCode());
result = prime * result + ((myTerm == null) ? 0 : myTerm.hashCode());
result = prime * result + ((myVersion == null) ? 0 : myVersion.hashCode());
result = prime * result + Boolean.hashCode(myUserSelected);
result = prime * result + Objects.hashCode(myScheme);
result = prime * result + Objects.hashCode(myTerm);
result = prime * result + Objects.hashCode(myVersion);
result = prime * result + Objects.hashCode(myUserSelected);
return result;
}
@ -237,7 +220,9 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
}
@Override
public boolean getUserSelected() { return myUserSelected; }
public boolean getUserSelected() { return myUserSelected != null && myUserSelected; }
public Boolean getUserSelectedBoolean() { return myUserSelected; }
@Override
public IBaseCoding setUserSelected(boolean theUserSelected) {
@ -245,4 +230,8 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
return this;
}
public void setUserSelectedBoolean(Boolean theUserSelected) {
myUserSelected = theUserSelected;
}
}

View File

@ -725,73 +725,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
if (theResource instanceof IResource) {
IResource resource = (IResource) theResource;
// Object securityLabelRawObj =
List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
profiles = super.getProfileTagsForEncoding(resource, profiles);
TagList tags = getMetaTagsForEncoding(resource, theEncodeContext);
InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
IdDt resourceId = resource.getId();
String versionIdPart = resourceId.getVersionIdPart();
if (isBlank(versionIdPart)) {
versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
}
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource);
if (super.shouldEncodeResourceMeta(resource) && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) || !extensionMetadataKeys.isEmpty()) {
beginObject(theEventWriter, "meta");
if (shouldEncodePath(resource, "meta.versionId")) {
writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart);
}
if (shouldEncodePath(resource, "meta.lastUpdated")) {
writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated);
}
if (profiles != null && profiles.isEmpty() == false) {
beginArray(theEventWriter, "profile");
for (IIdType profile : profiles) {
if (profile != null && isNotBlank(profile.getValue())) {
theEventWriter.write(profile.getValue());
}
}
theEventWriter.endArray();
}
if (securityLabels.isEmpty() == false) {
beginArray(theEventWriter, "security");
for (BaseCodingDt securityLabel : securityLabels) {
theEventWriter.beginObject();
theEncodeContext.pushPath("security", false);
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
theEncodeContext.popPath();
theEventWriter.endObject();
}
theEventWriter.endArray();
}
if (tags != null && tags.isEmpty() == false) {
beginArray(theEventWriter, "tag");
for (Tag tag : tags) {
if (tag.isEmpty()) {
continue;
}
theEventWriter.beginObject();
writeOptionalTagWithTextNode(theEventWriter, "system", tag.getScheme());
writeOptionalTagWithTextNode(theEventWriter, "code", tag.getTerm());
writeOptionalTagWithTextNode(theEventWriter, "display", tag.getLabel());
theEventWriter.endObject();
}
theEventWriter.endArray();
}
addExtensionMetadata(theResDef, theResource, theContainedResource, extensionMetadataKeys, resDef, theEventWriter, theEncodeContext);
theEventWriter.endObject(); // end meta
}
parseMetaForDSTU2(theResDef, theResource, theEventWriter, theContainedResource, theEncodeContext, resDef);
}
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
@ -799,6 +733,77 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
theEventWriter.endObject();
}
private void parseMetaForDSTU2(RuntimeResourceDefinition theResDef, IBaseResource theResource, BaseJsonLikeWriter theEventWriter, boolean theContainedResource, EncodeContext theEncodeContext, RuntimeResourceDefinition resDef) throws IOException {
IResource resource = (IResource) theResource;
// Object securityLabelRawObj =
List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
profiles = super.getProfileTagsForEncoding(resource, profiles);
TagList tags = getMetaTagsForEncoding(resource, theEncodeContext);
InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
IdDt resourceId = resource.getId();
String versionIdPart = resourceId.getVersionIdPart();
if (isBlank(versionIdPart)) {
versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
}
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource);
if (super.shouldEncodeResourceMeta(resource) && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) || !extensionMetadataKeys.isEmpty()) {
beginObject(theEventWriter, "meta");
if (shouldEncodePath(resource, "meta.versionId")) {
writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart);
}
if (shouldEncodePath(resource, "meta.lastUpdated")) {
writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated);
}
if (profiles != null && profiles.isEmpty() == false) {
beginArray(theEventWriter, "profile");
for (IIdType profile : profiles) {
if (profile != null && isNotBlank(profile.getValue())) {
theEventWriter.write(profile.getValue());
}
}
theEventWriter.endArray();
}
if (securityLabels.isEmpty() == false) {
beginArray(theEventWriter, "security");
for (BaseCodingDt securityLabel : securityLabels) {
theEventWriter.beginObject();
theEncodeContext.pushPath("security", false);
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
theEncodeContext.popPath();
theEventWriter.endObject();
}
theEventWriter.endArray();
}
if (tags != null && tags.isEmpty() == false) {
beginArray(theEventWriter, "tag");
for (Tag tag : tags) {
if (tag.isEmpty()) {
continue;
}
theEventWriter.beginObject();
writeOptionalTagWithTextNode(theEventWriter, "system", tag.getScheme());
writeOptionalTagWithTextNode(theEventWriter, "code", tag.getTerm());
writeOptionalTagWithTextNode(theEventWriter, "display", tag.getLabel());
// wipmb should we be writing the new properties here? There must be another path.
theEventWriter.endObject();
}
theEventWriter.endArray();
}
addExtensionMetadata(theResDef, theResource, theContainedResource, extensionMetadataKeys, resDef, theEventWriter, theEncodeContext);
theEventWriter.endObject(); // end meta
}
}
private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource,
boolean theContainedResource,

View File

@ -120,7 +120,9 @@ public enum VersionEnum {
V6_4_4,
V6_4_5,
V6_5_0,
V6_6_0
V6_6_0,
V6_7_0,
V6_8_0
;
public static VersionEnum latestVersion() {

View File

@ -27,7 +27,7 @@ public class TagTest {
@Test
public void testHashCode() {
Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label");
assertEquals(-1029266947, tag1.hashCode());
assertEquals(-1029268184, tag1.hashCode());
}
@Test

View File

@ -273,7 +273,9 @@ public class VersionCanonicalizer {
retVal.setSystem(coding.getSystem());
retVal.setDisplay(coding.getDisplay());
retVal.setVersion(coding.getVersion());
retVal.setUserSelected( ! coding.getUserSelectedElement().isEmpty() && coding.getUserSelected() );
if (!coding.getUserSelectedElement().isEmpty()) {
retVal.setUserSelected( coding.getUserSelected() );
}
return retVal;
}

View File

@ -245,6 +245,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
private IFulltextSearchSvc myFulltextSearchSvc;
@Autowired
private PlatformTransactionManager myTransactionManager;
protected final CodingSpy myCodingSpy = new CodingSpy();
@Autowired
protected IJpaStorageResourceParser myJpaStorageResourceParser;
@ -279,7 +282,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (tagList != null) {
for (Tag next : tagList) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.TAG, next.getScheme(), next.getTerm(),
next.getLabel(), next.getVersion(), next.getUserSelected());
next.getLabel(), next.getVersion(), myCodingSpy.getBooleanObject(next));
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
allDefs.add(tag);
@ -319,7 +322,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (tagList != null) {
for (IBaseCoding next : tagList) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.TAG, next.getSystem(), next.getCode(),
next.getDisplay(), next.getVersion(), next.getUserSelected());
next.getDisplay(), next.getVersion(), myCodingSpy.getBooleanObject(next));
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
@ -331,7 +334,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
List<? extends IBaseCoding> securityLabels = theResource.getMeta().getSecurity();
if (securityLabels != null) {
for (IBaseCoding next : securityLabels) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay(), null, null);
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion(), myCodingSpy.getBooleanObject(next));
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
@ -390,7 +393,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
MemoryCacheService.TagDefinitionCacheKey key = toTagDefinitionMemoryCacheKey(theTagType, theScheme, theTerm, theVersion, theUserSelected);
TagDefinition retVal = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.TAG_DEFINITION, key);
if (retVal == null) {
HashMap<MemoryCacheService.TagDefinitionCacheKey, TagDefinition> resolvedTagDefinitions = theTransactionDetails
.getOrCreateUserData(HapiTransactionService.XACT_USERDATA_KEY_RESOLVED_TAG_DEFINITIONS, HashMap::new);
@ -421,6 +423,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
String theVersion, Boolean theUserSelected) {
TypedQuery<TagDefinition> q = buildTagQuery(theTagType, theScheme, theTerm, theVersion, theUserSelected);
q.setMaxResults(1);
TransactionTemplate template = new TransactionTemplate(myTransactionManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
@ -462,7 +465,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
"Tag read/write failed: "
+ ex.getMessage() + ". "
+ "This is not a failure on its own, "
+ "but could be useful information in the result of an actual failure."
+ "but could be useful information in the result of an actual failure.", ex
);
throwables.add(ex);
}
@ -521,9 +524,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
? builder.isNull(from.get("myVersion"))
: builder.equal(from.get("myVersion"), theVersion));
predicates.add( isNull(theUserSelected) || isFalse(theUserSelected)
? builder.isFalse(from.get("myUserSelected"))
: builder.isTrue(from.get("myUserSelected")));
predicates.add( isNull(theUserSelected)
? builder.isNull(from.get("myUserSelected"))
: builder.equal(from.get("myUserSelected"), theUserSelected));
cq.where(predicates.toArray(new Predicate[0]));
return myEntityManager.createQuery(cq);
@ -552,7 +555,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
/**
* Returns true if the resource has changed (either the contents or the tags)
* Returns {@literal true} if the resource has changed (either the contents or the tags)
*/
protected EncodedResource populateResourceIntoEntity(TransactionDetails theTransactionDetails, RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, boolean thePerformIndexing) {
if (theEntity.getResourceType() == null) {
@ -794,6 +797,18 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
});
// Update the resource to contain the old tags
allTagsOld.forEach(tag -> {
IBaseCoding iBaseCoding = theResource.getMeta()
.addTag()
.setCode(tag.getTag().getCode())
.setSystem(tag.getTag().getSystem())
.setVersion(tag.getTag().getVersion());
if (tag.getTag().getUserSelected() != null) {
iBaseCoding.setUserSelected(tag.getTag().getUserSelected());
}
});
theEntity.setHasTags(!allTagsNew.isEmpty());
return !allTagsOld.equals(allTagsNew);
}

View File

@ -763,6 +763,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
if (!hasTag) {
theEntity.setHasTags(true);

View File

@ -38,6 +38,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.model.api.IResource;
@ -73,6 +74,7 @@ import java.util.List;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.cleanProvenanceSourceUri;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.decodeResource;
import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -348,19 +350,26 @@ public class JpaStorageResourceParser implements IJpaStorageResourceParser {
List<IBaseCoding> securityLabels = new ArrayList<>();
List<IdDt> profiles = new ArrayList<>();
for (BaseTag next : theTagList) {
switch (next.getTag().getTagType()) {
TagDefinition nextTag = next.getTag();
switch (nextTag.getTagType()) {
case PROFILE:
profiles.add(new IdDt(next.getTag().getCode()));
profiles.add(new IdDt(nextTag.getCode()));
break;
case SECURITY_LABEL:
IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt();
secLabel.setSystem(next.getTag().getSystem());
secLabel.setCode(next.getTag().getCode());
secLabel.setDisplay(next.getTag().getDisplay());
secLabel.setSystem(nextTag.getSystem());
secLabel.setCode(nextTag.getCode());
secLabel.setDisplay(nextTag.getDisplay());
// wipmb these technically support userSelected and version
securityLabels.add(secLabel);
break;
case TAG:
tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay()));
// wipmb check xml, etc.
Tag e = new Tag(nextTag.getSystem(), nextTag.getCode(), nextTag.getDisplay());
e.setVersion(nextTag.getVersion());
// careful! These are Boolean, not boolean.
e.setUserSelectedBoolean(nextTag.getUserSelected());
tagList.add(e);
break;
}
}
@ -432,7 +441,12 @@ public class JpaStorageResourceParser implements IJpaStorageResourceParser {
tag.setCode(next.getTag().getCode());
tag.setDisplay(next.getTag().getDisplay());
tag.setVersion(next.getTag().getVersion());
tag.setUserSelected(next.getTag().getUserSelected());
Boolean userSelected = next.getTag().getUserSelected();
// the tag is created with a null userSelected, but the api is primitive boolean.
// Only update if we are non-null.
if (nonNull(userSelected)) {
tag.setUserSelected(userSelected);
}
break;
}
}

View File

@ -1,5 +1,3 @@
package ca.uhn.fhir.jpa.migrate.tasks;
/*-
* #%L
* HAPI FHIR JPA Server
@ -19,8 +17,11 @@ package ca.uhn.fhir.jpa.migrate.tasks;
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.jpa.migrate.tasks;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.BulkExportJobEntity;
import ca.uhn.fhir.jpa.entity.BulkImportJobEntity;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
@ -37,8 +38,11 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.VersionEnum;
import software.amazon.awssdk.utils.StringUtils;
import java.util.Arrays;
import java.util.EnumSet;
@ -49,7 +53,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings({"SqlNoDataSourceInspection", "SpellCheckingInspection"})
@SuppressWarnings({"SqlNoDataSourceInspection", "SpellCheckingInspection", "java:S1192"})
public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
// H2, Derby, MariaDB, and MySql automatically add indexes to foreign keys
@ -87,13 +92,51 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
init600(); // 20211102 -
init610();
init620();
init630();
init640();
init640_after_20230126();
init660();
init680();
}
protected void init640() {
protected void init680() {
Builder version = forVersion(VersionEnum.V6_8_0);
// HAPI-FHIR #4801 - Add New Index On HFJ_RESOURCE
Builder.BuilderWithTableName resourceTable = version.onTable("HFJ_RESOURCE");
resourceTable
.addIndex("20230502.1", "IDX_RES_RESID_UPDATED")
.unique(false)
.online(true)
.withColumns("RES_ID", "RES_UPDATED", "PARTITION_ID");
Builder.BuilderWithTableName tagDefTable = version.onTable("HFJ_TAG_DEF");
tagDefTable.dropIndex("20230505.1", "IDX_TAGDEF_TYPESYSCODEVERUS");
tagDefTable.dropIndex("20230505.2", "IDX_TAG_DEF_TP_CD_SYS");
tagDefTable
.addIndex("20230505.3", "IDX_TAG_DEF_TP_CD_SYS")
.unique(false)
.online(false)
.withColumns("TAG_TYPE", "TAG_CODE", "TAG_SYSTEM", "TAG_ID", "TAG_VERSION", "TAG_USER_SELECTED");
}
protected void init660() {
Builder version = forVersion(VersionEnum.V6_6_0);
// fix Postgres clob types - that stupid oid driver problem is still there
// BT2_JOB_INSTANCE.PARAMS_JSON_LOB
version.onTable("BT2_JOB_INSTANCE")
.migratePostgresTextClobToBinaryClob("20230208.1", "PARAMS_JSON_LOB");
// BT2_JOB_INSTANCE.REPORT
version.onTable("BT2_JOB_INSTANCE")
.migratePostgresTextClobToBinaryClob("20230208.2", "REPORT");
// BT2_WORK_CHUNK.CHUNK_DATA
version.onTable("BT2_WORK_CHUNK")
.migratePostgresTextClobToBinaryClob("20230208.3", "CHUNK_DATA");
{
Builder.BuilderWithTableName tagDefTable = version.onTable("HFJ_TAG_DEF");
@ -120,6 +163,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
addTagDefConstraint.put(DriverTypeEnum.POSTGRES_9_4, "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODEVERUS UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_VERSION, TAG_USER_SELECTED)");
version.executeRawSql("20230209.5", addTagDefConstraint);
}
version
.onTable(Search.HFJ_SEARCH)
.addColumn("20230215.1", "SEARCH_UUID")
@ -137,20 +181,20 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.type(ColumnTypeEnum.STRING, 36);
Builder.BuilderAddTableByColumns resSearchUrlTable = version.addTableByColumns("20230227.1", "HFJ_RES_SEARCH_URL", "RES_SEARCH_URL");
Builder.BuilderAddTableByColumns resSearchUrlTable = version.addTableByColumns("20230227.1","HFJ_RES_SEARCH_URL", "RES_SEARCH_URL");
resSearchUrlTable.addColumn("RES_SEARCH_URL").nonNullable().type(ColumnTypeEnum.STRING, 768);
resSearchUrlTable.addColumn("RES_ID").nonNullable().type(ColumnTypeEnum.LONG);
resSearchUrlTable.addColumn( "RES_SEARCH_URL").nonNullable().type(ColumnTypeEnum.STRING, 768);
resSearchUrlTable.addColumn( "RES_ID").nonNullable().type(ColumnTypeEnum.LONG);
resSearchUrlTable.addColumn("CREATED_TIME").nonNullable().type(ColumnTypeEnum.DATE_TIMESTAMP);
resSearchUrlTable.addColumn( "CREATED_TIME").nonNullable().type(ColumnTypeEnum.DATE_TIMESTAMP);
resSearchUrlTable.addIndex("20230227.2", "IDX_RESSEARCHURL_RES").unique(false).withColumns("RES_ID");
resSearchUrlTable.addIndex("20230227.3", "IDX_RESSEARCHURL_TIME").unique(false).withColumns("CREATED_TIME");
}
}
protected void init630() {
protected void init640() {
Builder version = forVersion(VersionEnum.V6_3_0);
// start forced_id inline migration
@ -171,6 +215,15 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.onlyAppliesToPlatforms(NON_AUTOMATIC_FK_INDEX_PLATFORMS);
}
protected void init640_after_20230126() {
Builder version = forVersion(VersionEnum.V6_3_0);
{ //We added this constraint when userSelected and Version were added. It is no longer necessary.
Builder.BuilderWithTableName tagDefTable = version.onTable("HFJ_TAG_DEF");
tagDefTable.dropIndex("20230503.1", "IDX_TAGDEF_TYPESYSCODEVERUS");
}
}
private void init620() {
Builder version = forVersion(VersionEnum.V6_2_0);

View File

@ -47,11 +47,7 @@ import java.util.Collection;
@Table(
name = "HFJ_TAG_DEF",
indexes = {
@Index(name = "IDX_TAG_DEF_TP_CD_SYS", columnList = "TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_ID"),
},
uniqueConstraints = {
@UniqueConstraint(name = "IDX_TAGDEF_TYPESYSCODEVERUS",
columnNames = {"TAG_TYPE", "TAG_SYSTEM", "TAG_CODE", "TAG_VERSION", "TAG_USER_SELECTED"})
@Index(name = "IDX_TAG_DEF_TP_CD_SYS", columnList = "TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_ID, TAG_VERSION, TAG_USER_SELECTED"),
}
)
public class TagDefinition implements Serializable {
@ -162,14 +158,16 @@ public class TagDefinition implements Serializable {
}
}
/**
* Warning - this is nullable, while IBaseCoding getUserSelected isn't.
* wipmb maybe rename?
*/
public Boolean getUserSelected() {
// TODO: LD: this is not ideal as we are implicitly assuming null is false.
// Ideally we should fix IBaseCoding to return wrapper Boolean but that will involve another core/hapi release
return myUserSelected != null ? myUserSelected : false;
return myUserSelected;
}
public void setUserSelected(Boolean theUserSelected) {
myUserSelected = theUserSelected != null && theUserSelected;
myUserSelected = theUserSelected;
}

View File

@ -9,6 +9,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.system.HapiTestSystemProperties;
import ch.qos.logback.classic.Level;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding;
@ -41,6 +42,8 @@ import static org.hamcrest.Matchers.empty;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -153,6 +156,30 @@ public class FhirResourceDaoR4MetaTest extends BaseJpaR4Test {
assertEquals("bar", patient2.getMeta().getSecurityFirstRep().getCode());
}
/**
* Make sure round-trips with tags don't add a userSelected property.
* wipmb tag userSelected test
* Verify https://github.com/hapifhir/hapi-fhir/issues/4819
*/
@Test
void testAddTag_userSelectedStaysNull() {
// given
Patient patient1 = new Patient();
patient1.getMeta().addTag().setSystem("http://foo").setCode("bar");
patient1.setActive(true);
IIdType pid1 = myPatientDao.create(patient1).getId();
// when
var patientReadback = myPatientDao.read(pid1);
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(patientReadback));
// then
var tag = patientReadback.getMeta().getTag().get(0);
assertNotNull(tag);
assertNull(tag.getUserSelectedElement().asStringValue());
}
@Nested
public class TestTagWithVersionAndUserSelected {
@ -204,6 +231,7 @@ public class FhirResourceDaoR4MetaTest extends BaseJpaR4Test {
.setSystem(expectedSystem1)
.setCode(expectedCode1)
.setDisplay(expectedDisplay1);
assertNull(newTag.getUserSelectedElement().asStringValue());
assertFalse(newTag.getUserSelected());
newTag.setVersion(expectedVersion1)
.setUserSelected(expectedUserSelected1);

View File

@ -5,6 +5,8 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
@ -96,6 +98,33 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
}
@Test
public void testTagCollision() {
TagDefinition def = new TagDefinition();
def.setTagType(TagTypeEnum.TAG);
def.setSystem("system");
def.setCode("coding");
def.setDisplay("display");
def.setUserSelected(null);
Patient p = new Patient();
p.getMeta().addTag("system", "coding", "display");
myMemoryCacheService.invalidateAllCaches();
myPatientDao.create(p, new SystemRequestDetails());
//inject conflicting.
myTagDefinitionDao.saveAndFlush(def);
myMemoryCacheService.invalidateAllCaches();
myPatientDao.create(p, new SystemRequestDetails());
myMemoryCacheService.invalidateAllCaches();
myPatientDao.create(p, new SystemRequestDetails());
}
@Test
public void testCreateAndUpdateWithoutRequest() {
String methodName = "testUpdateByUrl";