Add UserSelected and Version to resource tag (#4591)

* First commit:  Add userSelected and version to ResourceTag and ResourceHistoryTag.  Add migration step to migration tasks.  For some reason SchemaMigrationTest is failing.

* Fix nullability, update constructors, truncate versions over 30 chars.

* yay we're done just cleanup

* Checkpoint for recent changes.

* Last commit before pausing work.

* Fix bad code to find the version, tweak unit test pid ID to versionless and add TODO about why is update part of the test not passing?

* Fix bad code in extractTagsRi():  Make it leverage the IBaseCoding variable already in the for loop.  Fix bad code in populateResourceMetadataRi().   Leverage the existing BaseTag and use it to populate the IBaseCoding tag instance instead.  Add more and better assertions in the unit test.

* Merge rel_6_4 in

* Current state for pairing

* Fix CodingDt.getUserSelected method return code in tinder

* Complete migration task and cleanup

* Remove missed fixme

* Bump version temporarily to use it for building core lib

* Use primitive for compatibility with tinder generated CodingDt

* Copyright updates

* Hack to bypass NPE generated by bogus tinder-generated code

* Simplify by removing temporary deprecations as version bump is needed anyway because of TagDefinition entity changes

* Adjust tests

* Fix migration task

* Add test to show problem with tinder generated BooleanDt

* More test adjustments

* Improve BooleanDt NPE avoidance

* Update core dep

* Reverse Juan CVE VersionEnumTest changes.

* Fix unit test.

---------

Co-authored-by: Ken Stevens <ken@smilecdr.com>
Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com>
Co-authored-by: Tadgh <garygrantgraham@gmail.com>
This commit is contained in:
Luke deGruchy 2023-02-27 12:58:27 -05:00 committed by GitHub
parent ce46f4dd6a
commit 9d70dbd08f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 359 additions and 63 deletions

View File

@ -19,15 +19,14 @@ package ca.uhn.fhir.model.api;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.net.URI;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import java.net.URI;
/**
* A single tag
* <p>
@ -115,12 +114,14 @@ 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;

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 4204
jira: SMILE-4688
title: "With default configuration, Resource meta.tag properties: `userSelected` and `version`, were not stored in the database.
This is now fixed."

View File

@ -126,6 +126,7 @@ import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
@ -143,7 +144,9 @@ import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.BooleanUtils.isFalse;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -276,7 +279,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tagList != null) {
for (Tag next : tagList) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.TAG, next.getScheme(), next.getTerm(),
next.getLabel(), next.getVersion(), next.getUserSelected());
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
allDefs.add(tag);
@ -288,7 +292,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
List<BaseCodingDt> securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource);
if (securityLabels != null) {
for (BaseCodingDt next : securityLabels) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(),
next.getCodeElement().getValue(), next.getDisplayElement().getValue(), null, null);
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
allDefs.add(tag);
@ -300,7 +305,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
List<IdDt> profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource);
if (profiles != null) {
for (IIdType next : profiles) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null, null, null);
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
allDefs.add(tag);
@ -314,7 +319,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
List<? extends IBaseCoding> tagList = theResource.getMeta().getTag();
if (tagList != null) {
for (IBaseCoding next : tagList) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.TAG, next.getSystem(), next.getCode(),
next.getDisplay(), next.getVersion(), next.getUserSelected());
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
@ -326,7 +332,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());
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay(), null, null);
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
@ -338,7 +344,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
List<? extends IPrimitiveType<String>> profiles = theResource.getMeta().getProfile();
if (profiles != null) {
for (IPrimitiveType<String> next : profiles) {
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
TagDefinition def = getTagOrNull(theTransactionDetails, TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null, null, null);
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
@ -376,22 +382,25 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
/**
* <code>null</code> will only be returned if the scheme and tag are both blank
*/
protected TagDefinition getTagOrNull(TransactionDetails theTransactionDetails, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
protected TagDefinition getTagOrNull(TransactionDetails theTransactionDetails, TagTypeEnum theTagType, String theScheme,
String theTerm, String theLabel, String theVersion, Boolean theUserSelected) {
if (isBlank(theScheme) && isBlank(theTerm) && isBlank(theLabel)) {
return null;
}
MemoryCacheService.TagDefinitionCacheKey key = toTagDefinitionMemoryCacheKey(theTagType, theScheme, theTerm);
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, () -> new HashMap<>());
HashMap<MemoryCacheService.TagDefinitionCacheKey, TagDefinition> resolvedTagDefinitions = theTransactionDetails
.getOrCreateUserData(HapiTransactionService.XACT_USERDATA_KEY_RESOLVED_TAG_DEFINITIONS, HashMap::new);
retVal = resolvedTagDefinitions.get(key);
if (retVal == null) {
// actual DB hit(s) happen here
retVal = getOrCreateTag(theTagType, theScheme, theTerm, theLabel);
retVal = getOrCreateTag(theTagType, theScheme, theTerm, theLabel, theVersion, theUserSelected);
TransactionSynchronization sync = new AddTagDefinitionToCacheAfterCommitSynchronization(key, retVal);
TransactionSynchronizationManager.registerSynchronization(sync);
@ -409,26 +418,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
* <p>
* Can also throw an InternalErrorException if something bad happens.
*/
private TagDefinition getOrCreateTag(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
Root<TagDefinition> from = cq.from(TagDefinition.class);
private TagDefinition getOrCreateTag(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel,
String theVersion, Boolean theUserSelected) {
if (isNotBlank(theScheme)) {
cq.where(
builder.and(
builder.equal(from.get("myTagType"), theTagType),
builder.equal(from.get("mySystem"), theScheme),
builder.equal(from.get("myCode"), theTerm)));
} else {
cq.where(
builder.and(
builder.equal(from.get("myTagType"), theTagType),
builder.isNull(from.get("mySystem")),
builder.equal(from.get("myCode"), theTerm)));
}
TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
TypedQuery<TagDefinition> q = buildTagQuery(theTagType, theScheme, theTerm, theVersion, theUserSelected);
TransactionTemplate template = new TransactionTemplate(myTransactionManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
@ -450,6 +443,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
val = q.getSingleResult();
} catch (NoResultException e) {
val = new TagDefinition(theTagType, theScheme, theTerm, theLabel);
val.setVersion(theVersion);
val.setUserSelected(theUserSelected);
myEntityManager.persist(val);
}
return val;
@ -507,6 +502,34 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
return retVal;
}
private TypedQuery<TagDefinition> buildTagQuery(TagTypeEnum theTagType, String theScheme, String theTerm,
String theVersion, Boolean theUserSelected) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
Root<TagDefinition> from = cq.from(TagDefinition.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(
builder.and(
builder.equal(from.get("myTagType"), theTagType),
builder.equal(from.get("myCode"), theTerm)));
predicates.add( isBlank(theScheme)
? builder.isNull(from.get("mySystem"))
: builder.equal(from.get("mySystem"), theScheme));
predicates.add( isBlank(theVersion)
? 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")));
cq.where(predicates.toArray(new Predicate[0]));
return myEntityManager.createQuery(cq);
}
void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
String newVersion;
@ -741,10 +764,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
if (def.isStandardType() == false) {
if ( ! def.isStandardType()) {
String profile = def.getResourceProfile("");
if (isNotBlank(profile)) {
TagDefinition profileDef = getTagOrNull(theTransactionDetails, TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
TagDefinition profileDef = getTagOrNull(theTransactionDetails, TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null, null, null);
ResourceTag tag = theEntity.addTag(profileDef);
allDefs.add(tag);
@ -1584,8 +1607,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
@Nonnull
public static MemoryCacheService.TagDefinitionCacheKey toTagDefinitionMemoryCacheKey(TagTypeEnum theTagType, String theScheme, String theTerm) {
return new MemoryCacheService.TagDefinitionCacheKey(theTagType, theScheme, theTerm);
public static MemoryCacheService.TagDefinitionCacheKey toTagDefinitionMemoryCacheKey(
TagTypeEnum theTagType, String theScheme, String theTerm, String theVersion, Boolean theUserSelected) {
return new MemoryCacheService.TagDefinitionCacheKey(theTagType, theScheme, theTerm, theVersion, theUserSelected);
}
static String cleanProvenanceSourceUri(String theProvenanceSourceUri) {

View File

@ -143,6 +143,7 @@ import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@ -740,9 +741,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
boolean hasTag = false;
for (BaseTag next : new ArrayList<>(theEntity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), nextDef.getTagType()) &&
ObjectUtil.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
ObjectUtil.equals(next.getTag().getCode(), nextDef.getCode())) {
if (Objects.equals(next.getTag().getTagType(), nextDef.getTagType()) &&
Objects.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
Objects.equals(next.getTag().getCode(), nextDef.getCode()) &&
Objects.equals(next.getTag().getVersion(), nextDef.getVersion()) &&
Objects.equals(next.getTag().getUserSelected(), nextDef.getUserSelected())) {
hasTag = true;
break;
}
@ -751,7 +754,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (!hasTag) {
theEntity.setHasTags(true);
TagDefinition def = getTagOrNull(theTransactionDetails, nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay());
TagDefinition def = getTagOrNull(theTransactionDetails, nextDef.getTagType(), nextDef.getSystem(),
nextDef.getCode(), nextDef.getDisplay(), nextDef.getVersion(), nextDef.getUserSelected());
if (def != null) {
BaseTag newEntity = theEntity.addTag(def);
if (newEntity.getTagId() == null) {

View File

@ -431,6 +431,8 @@ public class JpaStorageResourceParser implements IJpaStorageResourceParser {
tag.setSystem(next.getTag().getSystem());
tag.setCode(next.getTag().getCode());
tag.setDisplay(next.getTag().getDisplay());
tag.setVersion(next.getTag().getVersion());
tag.setUserSelected(next.getTag().getUserSelected());
break;
}
}

View File

@ -91,9 +91,38 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
}
protected void init640() {
Builder version = forVersion(VersionEnum.V6_4_0);
{
Builder.BuilderWithTableName tagDefTable = version.onTable("HFJ_TAG_DEF");
// add columns
tagDefTable
.addColumn("20230209.1", "TAG_VERSION")
.nullable()
.type(ColumnTypeEnum.STRING, 30);
tagDefTable
.addColumn("20230209.2", "TAG_USER_SELECTED")
.nullable()
.type(ColumnTypeEnum.BOOLEAN);
// Update indexing
tagDefTable.dropIndex("20230209.3", "IDX_TAGDEF_TYPESYSCODE");
tagDefTable.dropIndex("20230209.4", "IDX_TAGDEF_TYPESYSCODEVERUS");
Map<DriverTypeEnum, String> addTagDefConstraint = new HashMap<>();
addTagDefConstraint.put(DriverTypeEnum.H2_EMBEDDED, "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODEVERUS UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_VERSION, TAG_USER_SELECTED)");
addTagDefConstraint.put(DriverTypeEnum.MARIADB_10_1, "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODEVERUS UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_VERSION, TAG_USER_SELECTED)");
addTagDefConstraint.put(DriverTypeEnum.MSSQL_2012, "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODEVERUS UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_VERSION, TAG_USER_SELECTED)");
addTagDefConstraint.put(DriverTypeEnum.MYSQL_5_7, "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODEVERUS UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_VERSION, TAG_USER_SELECTED)");
addTagDefConstraint.put(DriverTypeEnum.ORACLE_12C, "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODEVERUS UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_VERSION, TAG_USER_SELECTED)");
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);
}
}
protected void init630() {
Builder version = forVersion(VersionEnum.V6_3_0);

View File

@ -32,10 +32,10 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.Session;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.GeneratorType;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.OptimisticLock;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.backend.types.Searchable;
@ -64,7 +64,6 @@ import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -49,7 +50,8 @@ import java.util.Collection;
@Index(name = "IDX_TAG_DEF_TP_CD_SYS", columnList = "TAG_TYPE, TAG_CODE, TAG_SYSTEM, TAG_ID"),
},
uniqueConstraints = {
@UniqueConstraint(name = "IDX_TAGDEF_TYPESYSCODE", columnNames = {"TAG_TYPE", "TAG_SYSTEM", "TAG_CODE"})
@UniqueConstraint(name = "IDX_TAGDEF_TYPESYSCODEVERUS",
columnNames = {"TAG_TYPE", "TAG_SYSTEM", "TAG_CODE", "TAG_VERSION", "TAG_USER_SELECTED"})
}
)
public class TagDefinition implements Serializable {
@ -57,22 +59,35 @@ public class TagDefinition implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "TAG_CODE", length = 200)
private String myCode;
@Column(name = "TAG_DISPLAY", length = 200)
private String myDisplay;
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_TAGDEF_ID")
@SequenceGenerator(name = "SEQ_TAGDEF_ID", sequenceName = "SEQ_TAGDEF_ID")
@Column(name = "TAG_ID")
private Long myId;
@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myTag")
private Collection<ResourceTag> myResources;
@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myTag")
private Collection<ResourceHistoryTag> myResourceVersions;
@Column(name = "TAG_SYSTEM", length = 200)
private String mySystem;
@Column(name = "TAG_TYPE", nullable = false)
@Enumerated(EnumType.ORDINAL)
private TagTypeEnum myTagType;
@Column(name = "TAG_VERSION", length = 30)
private String myVersion;
@Column(name = "TAG_USER_SELECTED")
private boolean myUserSelected;
@Transient
private transient Integer myHashCode;
@ -133,6 +148,27 @@ public class TagDefinition implements Serializable {
myHashCode = null;
}
public String getVersion() {
return myVersion;
}
public void setVersion(String theVersion) {
setVersionAfterTrim(theVersion);
}
private void setVersionAfterTrim(String theVersion) {
if (theVersion != null) {
myVersion = StringUtils.truncate(theVersion, 30);
}
}
public Boolean getUserSelected() { return myUserSelected; }
public void setUserSelected(Boolean theUserSelected) {
myUserSelected = theUserSelected != null && theUserSelected;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
@ -151,6 +187,8 @@ public class TagDefinition implements Serializable {
b.append(myTagType, other.myTagType);
b.append(mySystem, other.mySystem);
b.append(myCode, other.myCode);
b.append(myVersion, other.myVersion);
b.append(myUserSelected, other.myUserSelected);
}
return b.isEquals();
@ -163,6 +201,8 @@ public class TagDefinition implements Serializable {
b.append(myTagType);
b.append(mySystem);
b.append(myCode);
b.append(myVersion);
b.append(myUserSelected);
myHashCode = b.toHashCode();
}
return myHashCode;
@ -175,6 +215,8 @@ public class TagDefinition implements Serializable {
retVal.append("system", mySystem);
retVal.append("code", myCode);
retVal.append("display", myDisplay);
retVal.append("version", myVersion);
retVal.append("userSelected", myUserSelected);
return retVal.build();
}
}

View File

@ -13,7 +13,11 @@ public class TagDefinitionTest {
def.setCode("my_code");
def.setSystem("my_system");
def.setDisplay("my_display");
assertEquals("TagDefinition[id=<null>,system=my_system,code=my_code,display=my_display]", def.toString());
def.setVersion("V 1.0");
def.setUserSelected(true);
assertEquals(
"TagDefinition[id=<null>,system=my_system,code=my_code,display=my_display,version=V 1.0,userSelected=true]",
def.toString());
}
@Test
@ -39,7 +43,8 @@ public class TagDefinitionTest {
def.setCode("my_code");
def.setSystem("my_system");
def.setDisplay("my_display");
assertEquals (-2125810377,def.hashCode());
assertEquals (-2125810377,def.hashCode());
def.setVersion("V 1.0");
def.setUserSelected(true);
assertEquals (434166476,def.hashCode());
}
}

View File

@ -65,8 +65,8 @@ public class ExpandResourcesStepJpaTest extends BaseJpaR4Test {
.boxed()
.map(t -> {
Patient p = new Patient();
p.getMeta().addTag().setSystem("http://static").setCode("tag");
p.getMeta().addTag().setSystem("http://dynamic").setCode("tag" + t);
p.getMeta().addTag().setSystem("http://static").setCode("tag").setUserSelected(true).setVersion("1");
p.getMeta().addTag().setSystem("http://dynamic").setCode("tag" + t).setUserSelected(true).setVersion("1");
return myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
}).toList();
assertEquals(count, ids.size());
@ -91,10 +91,10 @@ public class ExpandResourcesStepJpaTest extends BaseJpaR4Test {
verify(mySink, times(1)).accept(myWorkChunkCaptor.capture());
ExpandedResourcesList expandedResourceList = myWorkChunkCaptor.getValue();
assertEquals(10, expandedResourceList.getStringifiedResources().size());
assertThat(expandedResourceList.getStringifiedResources().get(0), containsString("{\"system\":\"http://static\",\"code\":\"tag\"}"));
assertThat(expandedResourceList.getStringifiedResources().get(0), containsString("{\"system\":\"http://dynamic\",\"code\":\"tag0\"}"));
assertThat(expandedResourceList.getStringifiedResources().get(1), containsString("{\"system\":\"http://static\",\"code\":\"tag\"}"));
assertThat(expandedResourceList.getStringifiedResources().get(1), containsString("{\"system\":\"http://dynamic\",\"code\":\"tag1\"}"));
assertThat(expandedResourceList.getStringifiedResources().get(0), containsString("{\"system\":\"http://static\",\"version\":\"1\",\"code\":\"tag\",\"userSelected\":true}"));
assertThat(expandedResourceList.getStringifiedResources().get(0), containsString("{\"system\":\"http://static\",\"version\":\"1\",\"code\":\"tag\",\"userSelected\":true}"));
assertThat(expandedResourceList.getStringifiedResources().get(1), containsString("{\"system\":\"http://static\",\"version\":\"1\",\"code\":\"tag\",\"userSelected\":true}"));
assertThat(expandedResourceList.getStringifiedResources().get(1), containsString("{\"system\":\"http://static\",\"version\":\"1\",\"code\":\"tag\",\"userSelected\":true}"));
// Verify query counts
assertEquals(theExpectedSelectQueries, myCaptureQueriesListener.countSelectQueries());

View File

@ -77,12 +77,14 @@ public class BaseHapiFhirDaoTest {
TagTypeEnum theEnum,
String theScheme,
String theTerm,
String theLabel) {
String theLabel,
String theVersion,
Boolean theUserSelected ) {
// we need to init synchronization due to what
// the underlying class is doing
try {
TransactionSynchronizationManager.initSynchronization();
return super.getTagOrNull(theDetails, theEnum, theScheme, theTerm, theLabel);
return super.getTagOrNull(theDetails, theEnum, theScheme, theTerm, theLabel, theVersion, theUserSelected);
} finally {
TransactionSynchronizationManager.clearSynchronization();
}
@ -175,12 +177,13 @@ public class BaseHapiFhirDaoTest {
String scheme = "http://localhost";
String term = "code123";
String label = "hollow world";
String version = "v1.0";
Boolean userSelected = true;
String raceConditionError = "Entity exists; if this is logged, you have race condition issues!";
TagDefinition tagDefinition = new TagDefinition(tagType,
scheme,
term,
label);
TagDefinition tagDefinition = new TagDefinition(tagType, scheme, term, label);
tagDefinition.setVersion(version);
tagDefinition.setUserSelected(userSelected);
// mock objects
CriteriaBuilder builder = getMockedCriteriaBuilder();
@ -270,7 +273,7 @@ public class BaseHapiFhirDaoTest {
Runnable task = () -> {
latch.countDown();
try {
TagDefinition retTag = myTestDao.getTagOrNull(new TransactionDetails(), tagType, scheme, term, label);
TagDefinition retTag = myTestDao.getTagOrNull(new TransactionDetails(), tagType, scheme, term, label, version, userSelected);
outcomes.put(retTag.hashCode(), retTag);
counter.incrementAndGet();
} catch (Exception ex) {
@ -330,6 +333,8 @@ public class BaseHapiFhirDaoTest {
String scheme = "http://localhost";
String term = "code123";
String label = "hollow world";
String version = "v1.0";
Boolean userSelected = true;
TransactionDetails transactionDetails = new TransactionDetails();
String exMsg = "Hi there";
String readError = "No read for you";
@ -358,7 +363,7 @@ public class BaseHapiFhirDaoTest {
// test
try {
myTestDao.getTagOrNull(transactionDetails, tagType, scheme, term, label);
myTestDao.getTagOrNull(transactionDetails, tagType, scheme, term, label, version, userSelected);
fail();
} catch (Exception ex) {
// verify

View File

@ -1,12 +1,17 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
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 org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.Patient;
@ -14,12 +19,16 @@ import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -29,7 +38,9 @@ import java.util.stream.Collectors;
import static ca.uhn.fhir.rest.api.Constants.PARAM_TAG;
import static org.hamcrest.MatcherAssert.assertThat;
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.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -142,6 +153,163 @@ public class FhirResourceDaoR4MetaTest extends BaseJpaR4Test {
assertEquals("bar", patient2.getMeta().getSecurityFirstRep().getCode());
}
@Nested
public class TestTagWithVersionAndUserSelected {
private static final String expectedSystem1 = "http://foo";
private static final String expectedCode1 = "code1";
private static final String expectedVersion1 = "testVersion1";
private static final String expectedDisplay1 = "Test1";
private static final boolean expectedUserSelected1 = true;
private static final String expectedSystem2 = "http://another.system";
private static final String expectedCode2 = "code2";
private static final String expectedVersion2 = "testVersion2";
private static final String expectedDisplay2 = "Test2";
private static final boolean expectedUserSelected2 = false;
private static final class TagQtyExpectations {
public int theTagQty;
public int theTagV1Qty;
public int theTagV2Qty;
public TagQtyExpectations(int theTheTagV1Qty, int theTheTagV2Qty) {
theTagQty = theTheTagV1Qty + theTheTagV2Qty;
theTagV1Qty = theTheTagV1Qty;
theTagV2Qty = theTheTagV2Qty;
}
}
private static final Map<DaoConfig.TagStorageModeEnum, TagQtyExpectations> expectedTagCountMap = Map.of(
DaoConfig.TagStorageModeEnum.INLINE, new TagQtyExpectations(0, 1),
DaoConfig.TagStorageModeEnum.NON_VERSIONED, new TagQtyExpectations(1, 1),
DaoConfig.TagStorageModeEnum.VERSIONED, new TagQtyExpectations(1, 1)
);
private static final Map<DaoConfig.TagStorageModeEnum, TagQtyExpectations> expectedHistoryTagCountMap = Map.of(
DaoConfig.TagStorageModeEnum.INLINE, new TagQtyExpectations(0, 0),
DaoConfig.TagStorageModeEnum.NON_VERSIONED, new TagQtyExpectations(0, 0),
DaoConfig.TagStorageModeEnum.VERSIONED, new TagQtyExpectations(2, 1)
);
@ParameterizedTest
@EnumSource(DaoConfig.TagStorageModeEnum.class)
public void testAddTagWithVersionAndUserSelected(DaoConfig.TagStorageModeEnum theTagStorageModeEnum) {
myDaoConfig.setTagStorageMode(theTagStorageModeEnum);
final Patient savedPatient = new Patient();
final Coding newTag = savedPatient.getMeta()
.addTag()
.setSystem(expectedSystem1)
.setCode(expectedCode1)
.setDisplay(expectedDisplay1);
assertFalse(newTag.getUserSelected());
newTag.setVersion(expectedVersion1)
.setUserSelected(expectedUserSelected1);
savedPatient.setActive(true);
final IIdType pid1 = myPatientDao.create(savedPatient, new SystemRequestDetails()).getId().toVersionless();
final Patient retrievedPatient = myPatientDao.read(pid1, new SystemRequestDetails());
validateSavedPatientTags(retrievedPatient);
// Update the patient to create a ResourceHistoryTag record
final List<Coding> tagsFromDbPatient = retrievedPatient.getMeta().getTag();
assertEquals(1, tagsFromDbPatient.size());
tagsFromDbPatient.get(0)
.setCode(expectedCode2)
.setSystem(expectedSystem2)
.setVersion(expectedVersion2)
.setDisplay(expectedDisplay2)
.setUserSelected(expectedUserSelected2);
myPatientDao.update(retrievedPatient, new SystemRequestDetails());
final Patient retrievedUpdatedPatient = myPatientDao.read(pid1, new SystemRequestDetails());
final Meta meta = retrievedUpdatedPatient.getMeta();
final List<Coding> tags = meta.getTag();
tags.forEach(innerTag -> ourLog.info("TAGS: version: {}, userSelected: {}, code: {}, display: {}, system: {}",
innerTag.getVersion(), innerTag.getUserSelected(), innerTag.getCode(), innerTag.getDisplay(), innerTag.getSystem()));
final Coding tagFirstRep = meta.getTagFirstRep();
ourLog.info("TAG FIRST REP: version: {}, userSelected: {}, code: {}, display: {}, system: {}",
tagFirstRep.getVersion(), tagFirstRep.getUserSelected(), tagFirstRep.getCode(), tagFirstRep.getDisplay(), tagFirstRep.getSystem());
TagQtyExpectations expectedCounts = expectedTagCountMap.get(theTagStorageModeEnum);
validateUpdatedPatientTags(expectedCounts, tags);
final List<ResourceHistoryTag> resourceHistoryTags2 = myResourceHistoryTagDao.findAll();
resourceHistoryTags2.forEach(historyTag -> {
final TagDefinition tag = historyTag.getTag();
ourLog.info("tagId: {}, resourceId: {}, version: {}, userSelected: {}, system: {}, code: {}, display: {}",
historyTag.getTagId(), historyTag.getResourceId(), tag.getVersion(), tag.getUserSelected(), tag.getSystem(), tag.getCode(), tag.getDisplay());
});
TagQtyExpectations expectedHistoryCounts = expectedHistoryTagCountMap.get(theTagStorageModeEnum);
validateHistoryTags(expectedHistoryCounts, resourceHistoryTags2);
}
private void validateSavedPatientTags(Patient thePatient) {
assertAll(
() -> assertEquals(1, thePatient.getMeta().getTag().size()),
() -> assertEquals(0, thePatient.getMeta().getSecurity().size()),
() -> assertEquals(0, thePatient.getMeta().getProfile().size()),
() -> assertEquals(expectedSystem1, thePatient.getMeta().getTagFirstRep().getSystem()),
() -> assertTrue(thePatient.getMeta().getTagFirstRep().getUserSelected()),
() -> assertEquals(expectedCode1, thePatient.getMeta().getTagFirstRep().getCode()),
() -> assertEquals(expectedVersion1, thePatient.getMeta().getTagFirstRep().getVersion()),
() -> assertEquals(expectedUserSelected1, thePatient.getMeta().getTagFirstRep().getUserSelected())
);
}
private void validateUpdatedPatientTags(TagQtyExpectations theExpectedCounts, List<Coding> tags) {
assertAll(
() -> assertEquals(theExpectedCounts.theTagQty, tags.size()),
() -> assertEquals(theExpectedCounts.theTagV1Qty, tags.stream()
.filter(tag -> expectedSystem1.equals(tag.getSystem()))
.filter(tag -> expectedCode1.equals(tag.getCode()))
.filter(tag -> expectedDisplay1.equals(tag.getDisplay()))
.filter(tag -> expectedVersion1.equals(tag.getVersion()))
.filter(tag -> expectedUserSelected1 == tag.getUserSelected())
.count()),
() -> assertEquals(theExpectedCounts.theTagV2Qty, tags.stream()
.filter(tag -> expectedSystem2.equals(tag.getSystem()))
.filter(tag -> expectedCode2.equals(tag.getCode()))
.filter(tag -> expectedDisplay2.equals(tag.getDisplay()))
.filter(tag -> expectedVersion2.equals(tag.getVersion()))
.filter(tag -> expectedUserSelected2 == tag.getUserSelected())
.count())
);
}
private void validateHistoryTags(TagQtyExpectations theExpectedHistoryCounts, List<ResourceHistoryTag> theHistoryTags) {
// validating this way because tags are a set so can be in any order
assertAll(
() -> assertEquals(theExpectedHistoryCounts.theTagQty, theHistoryTags.size()),
() -> assertEquals(theExpectedHistoryCounts.theTagV1Qty, theHistoryTags.stream()
.filter(resourceHistoryTag -> expectedSystem1.equals(resourceHistoryTag.getTag().getSystem()))
.filter(resourceHistoryTag -> expectedCode1.equals(resourceHistoryTag.getTag().getCode()))
.filter(resourceHistoryTag -> expectedDisplay1.equals(resourceHistoryTag.getTag().getDisplay()))
.filter(resourceHistoryTag -> expectedVersion1.equals(resourceHistoryTag.getTag().getVersion()))
.filter(resourceHistoryTag -> expectedUserSelected1 == resourceHistoryTag.getTag().getUserSelected())
.count()),
() -> assertEquals(theExpectedHistoryCounts.theTagV2Qty, theHistoryTags.stream()
.filter(resourceHistoryTag -> expectedSystem2.equals(resourceHistoryTag.getTag().getSystem()))
.filter(resourceHistoryTag -> expectedCode2.equals(resourceHistoryTag.getTag().getCode()))
.filter(resourceHistoryTag -> expectedDisplay2.equals(resourceHistoryTag.getTag().getDisplay()))
.filter(resourceHistoryTag -> expectedVersion2.equals(resourceHistoryTag.getTag().getVersion()))
.filter(resourceHistoryTag -> expectedUserSelected2 == resourceHistoryTag.getTag().getUserSelected())
.count())
);
}
}
@Disabled // TODO JA: This test fails regularly, need to get a dedicated connection pool for tag creation
@Test

View File

@ -47,13 +47,18 @@ class MemoryCacheServiceTest {
String system = "http://example.com";
TagTypeEnum type = TagTypeEnum.TAG;
String code = "t";
String version = "Ver 3.0";
Boolean userSelected = true;
MemoryCacheService.TagDefinitionCacheKey cacheKey = new MemoryCacheService.TagDefinitionCacheKey(type, system, code);
MemoryCacheService.TagDefinitionCacheKey cacheKey = new MemoryCacheService.TagDefinitionCacheKey(
type, system, code, version, userSelected);
TagDefinition retVal = mySvc.getIfPresent(MemoryCacheService.CacheEnum.TAG_DEFINITION, cacheKey);
assertThat(retVal, nullValue());
TagDefinition tagDef = new TagDefinition(type, system, code, "theLabel");
tagDef.setVersion(version);
tagDef.setUserSelected(userSelected);
mySvc.put(MemoryCacheService.CacheEnum.TAG_DEFINITION, cacheKey, tagDef);
retVal = mySvc.getIfPresent(MemoryCacheService.CacheEnum.TAG_DEFINITION, cacheKey);

View File

@ -217,16 +217,22 @@ public class MemoryCacheService {
private final TagTypeEnum myType;
private final String mySystem;
private final String myCode;
private final String myVersion;
private Boolean myUserSelected;
private final int myHashCode;
public TagDefinitionCacheKey(TagTypeEnum theType, String theSystem, String theCode) {
public TagDefinitionCacheKey(TagTypeEnum theType, String theSystem, String theCode, String theVersion, Boolean theUserSelected) {
myType = theType;
mySystem = theSystem;
myCode = theCode;
myVersion = theVersion;
myUserSelected = theUserSelected;
myHashCode = new HashCodeBuilder(17, 37)
.append(myType)
.append(mySystem)
.append(myCode)
.append(myVersion)
.append(myUserSelected)
.toHashCode();
}

View File

@ -864,7 +864,7 @@
</licenses>
<properties>
<fhir_core_version>5.6.97</fhir_core_version>
<fhir_core_version>5.6.971</fhir_core_version>
<ucum_version>1.0.3</ucum_version>
<surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args>