Performance enhancements on the CapabilityStatement generator for JPA

server
This commit is contained in:
James Agnew 2018-04-28 11:06:42 -04:00
parent e299b062a6
commit 593a705365
39 changed files with 1033 additions and 571 deletions

View File

@ -619,12 +619,14 @@ public class IdDt extends UriDt implements /*IPrimitiveDatatype<String>, */IIdTy
/**
* Creates a new instance of this ID which is identical, but refers to the specific version of this resource ID noted by theVersion.
*
* @param theVersion The actual version string, e.g. "1"
* @param theVersion The actual version string, e.g. "1". If theVersion is blank or null, returns the same as {@link #toVersionless()}}
* @return A new instance of IdDt which is identical, but refers to the specific version of this resource ID noted by theVersion.
*/
@Override
public IdDt withVersion(String theVersion) {
Validate.notBlank(theVersion, "Version may not be null or empty");
if (isBlank(theVersion)) {
return toVersionless();
}
if (isLocal() || isUrn()) {
return new IdDt(getValueAsString());

View File

@ -4,8 +4,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.SingleItemLoadingCache;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
@ -17,6 +19,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Map;
/*
* #%L
* HAPI FHIR JPA Server
@ -113,6 +117,13 @@ public class BaseDstu2Config extends BaseConfig {
return retVal;
}
@Bean(name = "myResourceCountsCache")
public SingleItemLoadingCache<Map<String, Long>> resourceCountsCache() {
SingleItemLoadingCache<Map<String, Long>> retVal = new SingleItemLoadingCache<>(() -> systemDaoDstu2().getResourceCounts());
retVal.setCacheMillis(60 * DateUtils.MILLIS_PER_SECOND);
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IHapiTerminologySvc terminologyService() {
return new HapiTerminologySvcDstu2();

View File

@ -14,8 +14,10 @@ import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl;
import ca.uhn.fhir.jpa.util.SingleItemLoadingCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.utils.IResourceValidator;
@ -26,6 +28,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Map;
/*
* #%L
* HAPI FHIR JPA Server
@ -35,9 +39,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
* 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.
@ -77,6 +81,14 @@ public class BaseDstu3Config extends BaseConfig {
return retVal;
}
@Bean(name = "myResourceCountsCache")
public SingleItemLoadingCache<Map<String, Long>> resourceCountsCache() {
SingleItemLoadingCache<Map<String, Long>> retVal = new SingleItemLoadingCache<>(() -> systemDaoDstu3().getResourceCounts());
retVal.setCacheMillis(60 * DateUtils.MILLIS_PER_SECOND);
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDaoDstu3() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();

View File

@ -15,8 +15,10 @@ import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl;
import ca.uhn.fhir.jpa.util.SingleItemLoadingCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
@ -29,6 +31,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Map;
/*
* #%L
* HAPI FHIR JPA Server
@ -38,9 +42,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
* 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.
@ -92,6 +96,13 @@ public class BaseR4Config extends BaseConfig {
return retVal;
}
@Bean(name = "myResourceCountsCache")
public SingleItemLoadingCache<Map<String, Long>> resourceCountsCache() {
SingleItemLoadingCache<Map<String, Long>> retVal = new SingleItemLoadingCache<>(() -> systemDaoR4().getResourceCounts());
retVal.setCacheMillis(60 * DateUtils.MILLIS_PER_SECOND);
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDaoR4() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao;
* 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.
@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
@ -79,9 +80,12 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nullable;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
@ -717,14 +721,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource);
}
private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set<TagDefinition> allDefs) {
private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set<ResourceTag> allDefs) {
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tagList != null) {
for (Tag next : tagList) {
TagDefinition tag = getTagOrNull(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
if (tag != null) {
TagDefinition def = getTagOrNull(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
allDefs.add(tag);
theEntity.addTag(tag);
theEntity.setHasTags(true);
}
}
@ -733,10 +737,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
List<BaseCodingDt> securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource);
if (securityLabels != null) {
for (BaseCodingDt next : securityLabels) {
TagDefinition tag = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
if (tag != null) {
TagDefinition def = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
allDefs.add(tag);
theEntity.addTag(tag);
theEntity.setHasTags(true);
}
}
@ -745,24 +749,24 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
List<IdDt> profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource);
if (profiles != null) {
for (IIdType next : profiles) {
TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
if (tag != null) {
TagDefinition def = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
allDefs.add(tag);
theEntity.addTag(tag);
theEntity.setHasTags(true);
}
}
}
}
private void extractTagsRi(IAnyResource theResource, ResourceTable theEntity, Set<TagDefinition> allDefs) {
private void extractTagsRi(IAnyResource theResource, ResourceTable theEntity, Set<ResourceTag> theAllTags) {
List<? extends IBaseCoding> tagList = theResource.getMeta().getTag();
if (tagList != null) {
for (IBaseCoding next : tagList) {
TagDefinition tag = getTagOrNull(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
if (tag != null) {
allDefs.add(tag);
theEntity.addTag(tag);
TagDefinition def = getTagOrNull(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
theEntity.setHasTags(true);
}
}
@ -771,10 +775,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
List<? extends IBaseCoding> securityLabels = theResource.getMeta().getSecurity();
if (securityLabels != null) {
for (IBaseCoding next : securityLabels) {
TagDefinition tag = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay());
if (tag != null) {
allDefs.add(tag);
theEntity.addTag(tag);
TagDefinition def = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay());
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
theEntity.setHasTags(true);
}
}
@ -783,10 +787,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
List<? extends IPrimitiveType<String>> profiles = theResource.getMeta().getProfile();
if (profiles != null) {
for (IPrimitiveType<String> next : profiles) {
TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
if (tag != null) {
allDefs.add(tag);
theEntity.addTag(tag);
TagDefinition def = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
if (def != null) {
ResourceTag tag = theEntity.addTag(def);
theAllTags.add(tag);
theEntity.setHasTags(true);
}
}
@ -876,10 +880,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount);
}
private Set<TagDefinition> getAllTagDefinitions(ResourceTable theEntity) {
HashSet<TagDefinition> retVal = Sets.newHashSet();
for (ResourceTag next : theEntity.getTags()) {
retVal.add(next.getTag());
private Set<ResourceTag> getAllTagDefinitions(ResourceTable theEntity) {
HashSet<ResourceTag> retVal = Sets.newHashSet();
if (theEntity.isHasTags()) {
for (ResourceTag next : theEntity.getTags()) {
retVal.add(next);
}
}
return retVal;
}
@ -1173,7 +1179,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/**
* Returns true if the resource has changed (either the contents or the tags)
*/
protected EncodedResource populateResourceIntoEntity(IBaseResource theResource, ResourceTable theEntity, boolean theUpdateHash) {
protected EncodedResource populateResourceIntoEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, boolean theUpdateHash) {
if (theEntity.getResourceType() == null) {
theEntity.setResourceType(toResourceName(theResource));
}
@ -1222,11 +1228,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setHashSha256(hashSha256);
}
Set<TagDefinition> allDefs = new HashSet<>();
theEntity.setHasTags(false);
Set<TagDefinition> allTagsOld = getAllTagDefinitions(theEntity);
Set<ResourceTag> allDefs = new HashSet<>();
Set<ResourceTag> allTagsOld = getAllTagDefinitions(theEntity);
if (theResource instanceof IResource) {
extractTagsHapi((IResource) theResource, theEntity, allDefs);
@ -1238,32 +1241,33 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (def.isStandardType() == false) {
String profile = def.getResourceProfile("");
if (isNotBlank(profile)) {
TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
if (tag != null) {
TagDefinition profileDef = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
if (def != null) {
ResourceTag tag = theEntity.addTag(profileDef);
allDefs.add(tag);
theEntity.addTag(tag);
theEntity.setHasTags(true);
}
}
}
ArrayList<ResourceTag> existingTags = new ArrayList<>();
if (theEntity.isHasTags()) {
existingTags.addAll(theEntity.getTags());
}
for (ResourceTag next : existingTags) {
TagDefinition nextDef = next.getTag();
if (!allDefs.contains(nextDef)) {
if (shouldDroppedTagBeRemovedOnUpdate(theEntity, next)) {
// Don't keep duplicate tags
Set<TagDefinition> allDefsPresent = new HashSet<>();
theEntity.getTags().removeIf(theResourceTag -> !allDefsPresent.add(theResourceTag.getTag()));
// Remove any tags that have been removed
for (ResourceTag next : allTagsOld) {
if (!allDefs.contains(next)) {
if (shouldDroppedTagBeRemovedOnUpdate(theRequest, next)) {
theEntity.getTags().remove(next);
}
}
}
Set<TagDefinition> allTagsNew = getAllTagDefinitions(theEntity);
Set<ResourceTag> allTagsNew = getAllTagDefinitions(theEntity);
if (!allTagsOld.equals(allTagsNew)) {
changed = true;
}
theEntity.setHasTags(!allTagsNew.isEmpty());
} else {
theEntity.setHashSha256(null);
@ -1486,17 +1490,47 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* The default implementation removes any profile declarations, but leaves tags and security labels in place. Subclasses may choose to override and change this behaviour.
* </p>
* <p>
* See <a href="http://hl7.org/fhir/2015Sep/resource.html#1.11.3.7">Updates to Tags, Profiles, and Security Labels</a> for a description of the logic that the default behaviour folows.
* See <a href="http://hl7.org/fhir/resource.html#tag-updates">Updates to Tags, Profiles, and Security Labels</a> for a description of the logic that the default behaviour folows.
* </p>
*
* @param theEntity The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag The tag
* @param theTag The tag
* @return Returns <code>true</code> if the tag should be removed
*/
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
if (theTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
protected boolean shouldDroppedTagBeRemovedOnUpdate(RequestDetails theRequest, ResourceTag theTag) {
Set<TagTypeEnum> metaSnapshotModeTokens = null;
if (theRequest != null) {
List<String> metaSnapshotMode = theRequest.getHeaders(JpaConstants.HEADER_META_SNAPSHOT_MODE);
if (metaSnapshotMode != null && !metaSnapshotMode.isEmpty()) {
metaSnapshotModeTokens = new HashSet<>();
for (String nextHeaderValue : metaSnapshotMode) {
StringTokenizer tok = new StringTokenizer(nextHeaderValue, ",");
while (tok.hasMoreTokens()) {
switch (trim(tok.nextToken())) {
case "TAG":
metaSnapshotModeTokens.add(TagTypeEnum.TAG);
break;
case "PROFILE":
metaSnapshotModeTokens.add(TagTypeEnum.PROFILE);
break;
case "SECURITY_LABEL":
metaSnapshotModeTokens.add(TagTypeEnum.SECURITY_LABEL);
break;
}
}
}
}
}
if (metaSnapshotModeTokens == null) {
metaSnapshotModeTokens = Collections.singleton(TagTypeEnum.PROFILE);
}
if (metaSnapshotModeTokens.contains(theTag.getTag().getTagType())) {
return true;
}
return false;
}
@ -1514,7 +1548,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@SuppressWarnings("unchecked")
@Override
public <R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) {
public <R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity,
boolean theForHistoryOperation) {
ResourceHistoryTable history;
if (theEntity instanceof ResourceHistoryTable) {
@ -1639,7 +1674,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
@SuppressWarnings("unchecked")
protected ResourceTable updateEntity(final IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
protected ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable
theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ourLog.debug("Starting entity update");
@ -1732,7 +1768,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setNarrativeTextParsedIntoWords(null);
theEntity.setContentTextParsedIntoWords(null);
theEntity.setHashSha256(null);
changed = populateResourceIntoEntity(theResource, theEntity, true);
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
} else {
@ -1844,7 +1880,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
*/
compositeStringUniques = extractCompositeStringUniques(theEntity, stringParams, tokenParams, numberParams, quantityParams, dateParams, uriParams, links);
changed = populateResourceIntoEntity(theResource, theEntity, true);
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
theEntity.setUpdated(theUpdateTime);
if (theResource instanceof IResource) {
@ -1874,7 +1910,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} else {
changed = populateResourceIntoEntity(theResource, theEntity, false);
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false);
theEntity.setUpdated(theUpdateTime);
// theEntity.setLanguage(theResource.getLanguage().getValue());
@ -2062,11 +2098,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return theEntity;
}
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable entity, Date theDeletedTimestampOrNull, Date theUpdateTime) {
return updateEntity(theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable
entity, Date theDeletedTimestampOrNull, Date theUpdateTime) {
return updateEntity(theRequest, theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
}
public ResourceTable updateInternal(T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, ResourceTable theEntity, IIdType theResourceId, IBaseResource theOldResource) {
public ResourceTable updateInternal(RequestDetails theRequest, T theResource, boolean thePerformIndexing,
boolean theForceUpdateVersion, RequestDetails theRequestDetails, ResourceTable theEntity, IIdType
theResourceId, IBaseResource theOldResource) {
// Notify interceptors
ActionRequestDetails requestDetails = null;
if (theRequestDetails != null) {
@ -2085,7 +2124,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
// Perform update
ResourceTable savedEntity = updateEntity(theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
ResourceTable savedEntity = updateEntity(theRequest, theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
/*
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
@ -2271,14 +2310,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* @param theResourceType E.g. <code>Patient
* @param thePartsChoices E.g. <code>[[gender=male], [name=SMITH, name=JOHN]]</code>
*/
public static Set<String> extractCompositeStringUniquesValueChains(String theResourceType, List<List<String>> thePartsChoices) {
public static Set<String> extractCompositeStringUniquesValueChains(String
theResourceType, List<List<String>> thePartsChoices) {
for (List<String> next : thePartsChoices) {
for (Iterator<String> iter = next.iterator(); iter.hasNext(); ) {
if (isBlank(iter.next())) {
iter.remove();
}
}
next.removeIf(StringUtils::isBlank);
if (next.isEmpty()) {
return Collections.emptySet();
}
@ -2288,19 +2324,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return Collections.emptySet();
}
Collections.sort(thePartsChoices, new Comparator<List<String>>() {
@Override
public int compare(List<String> o1, List<String> o2) {
String str1 = null;
String str2 = null;
if (o1.size() > 0) {
str1 = o1.get(0);
}
if (o2.size() > 0) {
str2 = o2.get(0);
}
return StringUtils.compare(str1, str2);
thePartsChoices.sort((o1, o2) -> {
String str1 = null;
String str2 = null;
if (o1.size() > 0) {
str1 = o1.get(0);
}
if (o2.size() > 0) {
str2 = o2.get(0);
}
return compare(str1, str2);
});
List<String> values = new ArrayList<>();
@ -2309,7 +2342,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return queryStringsToPopulate;
}
private static void extractCompositeStringUniquesValueChains(String theResourceType, List<List<String>> thePartsChoices, List<String> theValues, Set<String> theQueryStringsToPopulate) {
private static void extractCompositeStringUniquesValueChains(String
theResourceType, List<List<String>> thePartsChoices, List<String> theValues, Set<String> theQueryStringsToPopulate) {
if (thePartsChoices.size() > 0) {
List<String> nextList = thePartsChoices.get(0);
Collections.sort(nextList);
@ -2446,14 +2480,15 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
private static List<BaseCodingDt> toBaseCodingList(List<IBaseCoding> theSecurityLabels) {
ArrayList<BaseCodingDt> retVal = new ArrayList<BaseCodingDt>(theSecurityLabels.size());
ArrayList<BaseCodingDt> retVal = new ArrayList<>(theSecurityLabels.size());
for (IBaseCoding next : theSecurityLabels) {
retVal.add((BaseCodingDt) next);
}
return retVal;
}
protected static Long translateForcedIdToPid(String theResourceName, String theResourceId, IForcedIdDao theForcedIdDao) {
protected static Long translateForcedIdToPid(String theResourceName, String theResourceId, IForcedIdDao
theForcedIdDao) {
return translateForcedIdToPids(new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0);
}
@ -2482,7 +2517,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
}
public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String theMatchUrl, RuntimeResourceDefinition resourceDef) {
public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String
theMatchUrl, RuntimeResourceDefinition resourceDef) {
SearchParameterMap paramMap = new SearchParameterMap();
List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);

View File

@ -46,6 +46,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
@ -175,7 +176,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public DaoMethodOutcome delete(IIdType theId, List<DeleteConflict> theDeleteConflicts, RequestDetails theRequestDetails) {
public DaoMethodOutcome delete(IIdType theId, List<DeleteConflict> theDeleteConflicts, RequestDetails theReques) {
if (theId == null || !theId.hasIdPart()) {
throw new InvalidRequestException("Can not perform delete, no ID provided");
}
@ -208,12 +209,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
T resourceToDelete = toResource(myResourceType, entity, false);
// Notify IServerOperationInterceptors about pre-action call
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourcePreDelete(resourceToDelete);
if (theReques != null) {
theReques.getRequestOperationCallback().resourcePreDelete(resourceToDelete);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourcePreDelete(theRequestDetails, resourceToDelete);
((IServerOperationInterceptor) next).resourcePreDelete(theReques, resourceToDelete);
}
}
@ -222,23 +223,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
preDelete(resourceToDelete, entity);
// Notify interceptors
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), theId.getResourceType(), theId);
if (theReques != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theReques, getContext(), theId.getResourceType(), theId);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
Date updateTime = new Date();
ResourceTable savedEntity = updateEntity(null, entity, updateTime, updateTime);
ResourceTable savedEntity = updateEntity(theReques, null, entity, updateTime, updateTime);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), theId.getResourceType(), theId);
theRequestDetails.getRequestOperationCallback().resourceDeleted(resourceToDelete);
if (theReques != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theReques, getContext(), theId.getResourceType(), theId);
theReques.getRequestOperationCallback().resourceDeleted(resourceToDelete);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceDeleted(theRequestDetails, resourceToDelete);
((IServerOperationInterceptor) next).resourceDeleted(theReques, resourceToDelete);
}
}
@ -272,7 +273,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
* transaction processors
*/
@Override
public DeleteMethodOutcome deleteByUrl(String theUrl, List<DeleteConflict> deleteConflicts, RequestDetails theRequestDetails) {
public DeleteMethodOutcome deleteByUrl(String theUrl, List<DeleteConflict> deleteConflicts, RequestDetails theRequest) {
StopWatch w = new StopWatch();
Set<Long> resource = processMatchUrl(theUrl, myResourceType);
@ -290,12 +291,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
T resourceToDelete = toResource(myResourceType, entity, false);
// Notify IServerOperationInterceptors about pre-action call
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourcePreDelete(resourceToDelete);
if (theRequest != null) {
theRequest.getRequestOperationCallback().resourcePreDelete(resourceToDelete);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourcePreDelete(theRequestDetails, resourceToDelete);
((IServerOperationInterceptor) next).resourcePreDelete(theRequest, resourceToDelete);
}
}
@ -303,24 +304,24 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Notify interceptors
IdDt idToDelete = entity.getIdDt();
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, idToDelete.getResourceType(), idToDelete);
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, idToDelete.getResourceType(), idToDelete);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
// Perform delete
Date updateTime = new Date();
updateEntity(null, entity, updateTime, updateTime);
updateEntity(theRequest, null, entity, updateTime, updateTime);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourceDeleted(resourceToDelete);
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, idToDelete.getResourceType(), idToDelete);
if (theRequest != null) {
theRequest.getRequestOperationCallback().resourceDeleted(resourceToDelete);
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, idToDelete.getResourceType(), idToDelete);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceDeleted(theRequestDetails, resourceToDelete);
((IServerOperationInterceptor) next).resourceDeleted(theRequest, resourceToDelete);
}
}
}
@ -366,7 +367,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequestDetails) {
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequest) {
StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource);
@ -405,23 +406,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
// Notify interceptors
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), theResource);
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getContext(), theResource);
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
}
// Notify JPA interceptors
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourcePreCreate(theResource);
if (theRequest != null) {
theRequest.getRequestOperationCallback().resourcePreCreate(theResource);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourcePreCreate(theRequestDetails, theResource);
((IServerOperationInterceptor) next).resourcePreCreate(theRequest, theResource);
}
}
// Perform actual DB update
ResourceTable updatedEntity = updateEntity(theResource, entity, null, thePerformIndexing, thePerformIndexing, theUpdateTime, false, thePerformIndexing);
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theUpdateTime, false, thePerformIndexing);
theResource.setId(entity.getIdDt());
@ -436,12 +437,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Notify JPA interceptors
if (!updatedEntity.isUnchangedInCurrentOperation()) {
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourceCreated(theResource);
if (theRequest != null) {
theRequest.getRequestOperationCallback().resourceCreated(theResource);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceCreated(theRequestDetails, theResource);
((IServerOperationInterceptor) next).resourceCreated(theRequest, theResource);
}
}
}
@ -931,7 +932,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public void reindex(T theResource, ResourceTable theEntity) {
ourLog.debug("Indexing resource {} - PID {}", theResource.getIdElement().getValue(), theEntity.getId());
CURRENTLY_REINDEXING.put(theResource, Boolean.TRUE);
updateEntity(theResource, theEntity, null, true, false, theEntity.getUpdatedDate(), true, false);
updateEntity(null, theResource, theEntity, null, true, false, theEntity.getUpdatedDate(), true, false);
CURRENTLY_REINDEXING.put(theResource, null);
}
@ -1232,7 +1233,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
/*
* If we aren't indexing, that means we're doing this inside a transaction.
* The transaction will do the actual storate to the database a bit later on,
* The transaction will do the actual storage to the database a bit later on,
* after placeholder IDs have been replaced, by calling {@link #updateInternal}
* directly. So we just bail now.
*/
@ -1245,7 +1246,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
/*
* Otherwise, we're not in a transaction
*/
ResourceTable savedEntity = updateInternal(theResource, thePerformIndexing, theForceUpdateVersion, theRequestDetails, entity, resourceId, oldResource);
ResourceTable savedEntity = updateInternal(theRequestDetails, theResource, thePerformIndexing, theForceUpdateVersion, theRequestDetails, entity, resourceId, oldResource);
DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false);
if (!thePerformIndexing) {

View File

@ -8,6 +8,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.ReindexFailureException;
import ca.uhn.fhir.jpa.util.SingleItemLoadingCache;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -15,6 +16,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
@ -23,16 +25,10 @@ import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.Query;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -74,6 +70,10 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
private PlatformTransactionManager myTxManager;
@Autowired
private IResourceTableDao myResourceTableDao;
@Autowired
@Qualifier("myResourceCountsCache")
public SingleItemLoadingCache<Map<String, Long>> myResourceCountsCache;
private int doPerformReindexingPass(final Integer theCount) {
/*
@ -165,23 +165,23 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
@Override
public Map<String, Long> getResourceCounts() {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<?> from = cq.from(ResourceTable.class);
cq.multiselect(from.get("myResourceType").as(String.class), builder.count(from.get("myResourceType")).as(Long.class));
cq.groupBy(from.get("myResourceType"));
TypedQuery<Tuple> q = myEntityManager.createQuery(cq);
Map<String, Long> retVal = new HashMap<>();
for (Tuple next : q.getResultList()) {
String resourceName = next.get(0, String.class);
Long count = next.get(1, Long.class);
retVal.put(resourceName, count);
List<Map<?,?>> counts = myResourceTableDao.getResourceCounts();
for (Map<?, ?> next : counts) {
retVal.put(next.get("type").toString(), Long.parseLong(next.get("count").toString()));
}
return retVal;
}
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
@Nullable
@Override
public Map<String, Long> getResourceCountsFromCache() {
return myResourceCountsCache.get();
}
@Override
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
if (theRequestDetails != null) {

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -39,8 +40,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@ -74,9 +73,9 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
if (theDeletedTimestampOrNull != null) {
mySubscriptionTableDao.deleteAllForSubscription(theEntity);

View File

@ -387,9 +387,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (updatedEntities.contains(nextOutcome.getEntity())) {
updateInternal(nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, updateTime, false, true);
updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, updateTime, false, true);
}
}

View File

@ -25,7 +25,10 @@ import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nullable;
import java.util.Date;
import java.util.Map;
@ -41,6 +44,14 @@ public interface IFhirSystemDao<T, MT> extends IDao {
Map<String, Long> getResourceCounts();
/**
*Returns a cached count of resources using a cache that regularly
* refreshes in the background. This method will never
*/
@Nullable
Map<String, Long> getResourceCountsFromCache();
IBundleProvider history(Date theDate, Date theUntil, RequestDetails theRequestDetails);
/**

View File

@ -8,6 +8,9 @@ import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Map;
/*
* #%L
* HAPI FHIR JPA Server
@ -42,6 +45,9 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
@Query("SELECT t.myId FROM ResourceTable t WHERE t.myIndexStatus IS NULL")
Slice<Long> findUnindexed(Pageable thePageRequest);
@Query("SELECT t.myResourceType as type, COUNT(*) as count FROM ResourceTable t GROUP BY t.myResourceType")
List<Map<?,?>> getResourceCounts();
@Modifying
@Query("UPDATE ResourceTable r SET r.myIndexStatus = null WHERE r.myResourceType = :restype")
int markResourcesOfTypeAsRequiringReindexing(@Param("restype") String theResourceType);

View File

@ -199,9 +199,9 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
}
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
CodeSystem cs = (CodeSystem) theResource;

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.dstu3.model.Subscription;
@ -82,9 +83,9 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
if (theDeletedTimestampOrNull != null) {
mySubscriptionTableDao.deleteAllForSubscription(theEntity);

View File

@ -545,9 +545,9 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (updatedEntities.contains(nextOutcome.getEntity())) {
updateInternal(nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
}
}

View File

@ -47,6 +47,7 @@ import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -172,9 +173,9 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4<CodeSystem> i
}
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
CodeSystem cs = (CodeSystem) theResource;

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -36,7 +37,6 @@ import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import javax.annotation.Nullable;
import java.util.Date;
@ -75,9 +75,9 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
if (theDeletedTimestampOrNull != null) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());

View File

@ -51,7 +51,6 @@ import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.UrlUtil.UrlParts;
import com.google.common.collect.ArrayListMultimap;
import javolution.io.Struct;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
import org.hl7.fhir.instance.model.api.*;
@ -317,7 +316,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
@SuppressWarnings("unchecked")
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set<IdType> theAllIds,
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(RequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set<IdType> theAllIds,
Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap<BundleEntryComponent, Integer> theOriginalRequestOrder, List<BundleEntryComponent> theEntries) {
Set<String> deletedResources = new HashSet<>();
List<DeleteConflict> deleteConflicts = new ArrayList<>();
@ -545,9 +544,9 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (updatedEntities.contains(nextOutcome.getEntity())) {
updateInternal(nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
}
}
@ -713,7 +712,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
private static void handleTransactionCreateOrUpdateOutcome(Map<IdType, IdType> idSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, IdType nextResourceId, DaoMethodOutcome outcome,
BundleEntryComponent newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) {
BundleEntryComponent newEntry, String theResourceType, IBaseResource theRes, RequestDetails theRequestDetails) {
IdType newId = (IdType) outcome.getId().toUnqualifiedVersionless();
IdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) {

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.util.SingleItemLoadingCache;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Conformance;
@ -44,6 +45,8 @@ import ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
public class JpaConformanceProviderDstu2 extends ServerConformanceProvider {
private volatile Conformance myCachedValue;
@ -52,16 +55,7 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider {
private boolean myIncludeResourceCounts;
private RestfulServer myRestfulServer;
private IFhirSystemDao<Bundle, MetaDt> mySystemDao;
/**
* Constructor
*/
@CoverageIgnore
public JpaConformanceProviderDstu2(){
super();
super.setCache(false);
setIncludeResourceCounts(true);
}
private SingleItemLoadingCache<Map<String, Long>> myResourceCountsCache;
/**
* Constructor
@ -79,10 +73,11 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider {
public Conformance getServerConformance(HttpServletRequest theRequest) {
Conformance retVal = myCachedValue;
Map<String, Long> counts = Collections.emptyMap();
Map<String, Long> counts = null;
if (myIncludeResourceCounts) {
counts = mySystemDao.getResourceCounts();
counts = mySystemDao.getResourceCountsFromCache();
}
counts = defaultIfNull(counts, Collections.emptyMap());
FhirContext ctx = myRestfulServer.getFhirContext();

View File

@ -25,7 +25,9 @@ import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.primitive.BooleanDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
@ -79,35 +81,33 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
@Operation(name = JpaConstants.OPERATION_NAME_EXPUNGE, idempotent = false, returnParameters = {
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = org.hl7.fhir.r4.model.IntegerType.class)
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerDt.class)
})
public Parameters expunge(
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
return JpaSystemProviderDstu2.toExpungeResponse(retVal);
}
@Operation(name = JpaConstants.OPERATION_NAME_EXPUNGE, idempotent = false, returnParameters = {
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = org.hl7.fhir.r4.model.IntegerType.class)
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerDt.class)
})
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
return JpaSystemProviderDstu2.toExpungeResponse(retVal);
}
//@formatter:off
@Operation(name = OPERATION_NAME_META, idempotent = true, returnParameters = {
@OperationParam(name = "return", type = MetaDt.class)
})
//@formatter:on
public Parameters meta(RequestDetails theRequestDetails) {
Parameters parameters = new Parameters();
MetaDt metaGetOperation = getDao().metaGetOperation(MetaDt.class, theRequestDetails);
@ -115,11 +115,9 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
return parameters;
}
//@formatter:off
@Operation(name = OPERATION_NAME_META, idempotent = true, returnParameters = {
@OperationParam(name = "return", type = MetaDt.class)
})
//@formatter:on
public Parameters meta(@IdParam IdDt theId, RequestDetails theRequestDetails) {
Parameters parameters = new Parameters();
MetaDt metaGetOperation = getDao().metaGetOperation(MetaDt.class, theId, theRequestDetails);

View File

@ -187,8 +187,8 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
public Parameters getResourceCounts() {
Parameters retVal = new Parameters();
Map<String, Long> counts = mySystemDao.getResourceCounts();
counts = new TreeMap<String, Long>(counts);
Map<String, Long> counts = mySystemDao.getResourceCountsFromCache();
counts = new TreeMap<>(counts);
for (Entry<String, Long> nextEntry : counts.entrySet()) {
retVal.addParameter().setName(new StringDt(nextEntry.getKey())).setValue(new IntegerDt(nextEntry.getValue().intValue()));
}

View File

@ -35,6 +35,8 @@ import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider {
private volatile CapabilityStatement myCachedValue;
@ -70,10 +72,11 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
public CapabilityStatement getServerConformance(HttpServletRequest theRequest) {
CapabilityStatement retVal = myCachedValue;
Map<String, Long> counts = Collections.emptyMap();
Map<String, Long> counts = null;
if (myIncludeResourceCounts) {
counts = mySystemDao.getResourceCounts();
counts = mySystemDao.getResourceCountsFromCache();
}
counts = defaultIfNull(counts, Collections.emptyMap());
retVal = super.getServerConformance(theRequest);
for (CapabilityStatementRestComponent nextRest : retVal.getRest()) {

View File

@ -31,9 +31,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -83,13 +81,13 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
}
@Operation(name = JpaConstants.OPERATION_NAME_EXPUNGE, idempotent = false, returnParameters = {
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = org.hl7.fhir.r4.model.IntegerType.class)
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
})
public Parameters expunge(
@IdParam IIdType theIdParam,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
try {
@ -100,12 +98,12 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
}
@Operation(name = JpaConstants.OPERATION_NAME_EXPUNGE, idempotent = false, returnParameters = {
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = org.hl7.fhir.r4.model.IntegerType.class)
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
})
public Parameters expunge(
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) org.hl7.fhir.r4.model.IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) org.hl7.fhir.r4.model.BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) org.hl7.fhir.r4.model.BooleanType theExpungeOldVersions
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions
) {
org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null);
try {
@ -139,11 +137,9 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
return parameters;
}
//@formatter:off
@Operation(name = OPERATION_NAME_META_ADD, idempotent = true, returnParameters = {
@OperationParam(name = "return", type = Meta.class)
})
//@formatter:on
public Parameters metaAdd(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
if (theMeta == null) {
throw new InvalidRequestException("Input contains no parameter with name 'meta'");
@ -154,11 +150,9 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
return parameters;
}
//@formatter:off
@Operation(name = OPERATION_NAME_META_DELETE, idempotent = true, returnParameters = {
@OperationParam(name = "return", type = Meta.class)
})
//@formatter:on
public Parameters metaDelete(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
if (theMeta == null) {
throw new InvalidRequestException("Input contains no parameter with name 'meta'");

View File

@ -191,8 +191,8 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
public Parameters getResourceCounts() {
Parameters retVal = new Parameters();
Map<String, Long> counts = mySystemDao.getResourceCounts();
counts = new TreeMap<String, Long>(counts);
Map<String, Long> counts = mySystemDao.getResourceCountsFromCache();
counts = new TreeMap<>(counts);
for (Entry<String, Long> nextEntry : counts.entrySet()) {
retVal.addParameter().setName((nextEntry.getKey())).setValue(new IntegerType(nextEntry.getValue().intValue()));
}

View File

@ -35,6 +35,8 @@ import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider {
private volatile CapabilityStatement myCachedValue;
@ -70,10 +72,11 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S
public CapabilityStatement getServerConformance(HttpServletRequest theRequest) {
CapabilityStatement retVal = myCachedValue;
Map<String, Long> counts = Collections.emptyMap();
Map<String, Long> counts = null;
if (myIncludeResourceCounts) {
counts = mySystemDao.getResourceCounts();
counts = mySystemDao.getResourceCountsFromCache();
}
counts = defaultIfNull(counts, Collections.emptyMap());
retVal = super.getServerConformance(theRequest);
for (CapabilityStatementRestComponent nextRest : retVal.getRest()) {

View File

@ -177,8 +177,8 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
public Parameters getResourceCounts() {
Parameters retVal = new Parameters();
Map<String, Long> counts = mySystemDao.getResourceCounts();
counts = new TreeMap<String, Long>(counts);
Map<String, Long> counts = mySystemDao.getResourceCountsFromCache();
counts = new TreeMap<>(counts);
for (Entry<String, Long> nextEntry : counts.entrySet()) {
retVal.addParameter().setName((nextEntry.getKey())).setValue(new IntegerType(nextEntry.getValue().intValue()));
}

View File

@ -92,4 +92,11 @@ public class JpaConstants {
* Output parameter name for the $expunge operation
*/
public static final String OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT = "count";
/**
* Header name for the "X-Meta-Snapshot-Mode" header, which
* specifies that properties in meta (tags, profiles, security labels)
* should be treated as a snapshot, meaning that these things will
* be removed if they are nt explicitly included in updates
*/
public static final String HEADER_META_SNAPSHOT_MODE = "X-Meta-Snapshot-Mode";
}

View File

@ -0,0 +1,91 @@
package ca.uhn.fhir.jpa.util;
/*-
* #%L
* Smile CDR - CDR
* %%
* Copyright (C) 2016 - 2018 Simpatico Intelligent Systems Inc
* %%
* All rights reserved.
* #L%
*/
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
/**
* This is a simple cache for CapabilityStatement resources to
* be returned as server metadata.
*/
public class SingleItemLoadingCache<T> {
private static final Logger ourLog = LoggerFactory.getLogger(SingleItemLoadingCache.class);
private static Long ourNowForUnitTest;
private final Callable<T> myFetcher;
private volatile long myCacheMillis;
private AtomicReference<T> myCapabilityStatement = new AtomicReference<>();
private long myLastFetched;
/**
* Constructor
*/
public SingleItemLoadingCache(Callable<T> theFetcher) {
myFetcher = theFetcher;
}
public synchronized void clear() {
ourLog.info("Clearning cache");
myCapabilityStatement.set(null);
myLastFetched = 0;
}
public synchronized T get() {
return myCapabilityStatement.get();
}
private T refresh() {
T retVal;
try {
retVal = myFetcher.call();
} catch (Exception e) {
throw new InternalErrorException(e);
}
myCapabilityStatement.set(retVal);
myLastFetched = now();
return retVal;
}
public void setCacheMillis(long theCacheMillis) {
myCacheMillis = theCacheMillis;
}
@Scheduled(fixedDelay = 60000)
public void update() {
if (myCacheMillis > 0) {
long now = now();
long expiry = now - myCacheMillis;
if (myLastFetched < expiry) {
refresh();
}
}
}
private static long now() {
if (ourNowForUnitTest != null) {
return ourNowForUnitTest;
}
return System.currentTimeMillis();
}
@VisibleForTesting
static void setNowForUnitTest(Long theNowForUnitTest) {
ourNowForUnitTest = theNowForUnitTest;
}
}

View File

@ -1,47 +0,0 @@
package ca.uhn.fhir.jpa.util;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
/**
* Utility to get the Spring proxy object's target object
*/
public class SpringObjectCaster {
/**
* Retrieve the Spring proxy object's target object
* @param proxy
* @param clazz
* @param <T>
* @return
* @throws Exception
*/
public static <T> T getTargetObject(Object proxy, Class<T> clazz) throws Exception {
while( (AopUtils.isJdkDynamicProxy(proxy))) {
return clazz.cast(getTargetObject(((Advised)proxy).getTargetSource().getTarget(), clazz));
}
return clazz.cast(proxy);
}
}

View File

@ -0,0 +1,149 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.hamcrest.Matchers;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
public class FhirResourceDaoR4UpdateTagSnapshotTest extends BaseJpaR4Test {
@Test
public void testUpdateWithDuplicateTagsWithHeader() {
when(mySrd.getHeaders(eq(JpaConstants.HEADER_META_SNAPSHOT_MODE))).thenReturn(Lists.newArrayList("TAG"));
Patient p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.getMeta().addTag("urn:foo", "bar2", "baz");
p.getMeta().addTag("urn:foo", "bar2", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
assertEquals(2, p.getMeta().getTag().size());
p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
// It would be nice if this didn't trigger a version update but
// i guess it's not so bad that it does
assertEquals("2", p.getIdElement().getVersionIdPart());
assertEquals(true, p.getActive());
assertEquals(1, p.getMeta().getTag().size());
}
@Test
public void testUpdateWithFewerTagsNoHeader() {
Patient p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.getMeta().addTag("urn:foo", "bar2", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
assertEquals("1", p.getIdElement().getVersionIdPart());
assertEquals(true, p.getActive());
assertEquals(2, p.getMeta().getTag().size());
assertEquals("urn:foo", p.getMeta().getTag().get(0).getSystem());
assertThat(p.getMeta().getTag().get(0).getCode(), Matchers.anyOf(Matchers.equalTo("bar"), Matchers.equalTo("bar2")));
}
@Test
public void testUpdateWithFewerTagsWithHeader() {
when(mySrd.getHeaders(eq(JpaConstants.HEADER_META_SNAPSHOT_MODE))).thenReturn(Lists.newArrayList("TAG"));
Patient p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.getMeta().addTag("urn:foo", "bar2", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
// It would be nice if this didn't trigger a version update but
// i guess it's not so bad that it does
assertEquals("2", p.getIdElement().getVersionIdPart());
assertEquals(true, p.getActive());
assertEquals(1, p.getMeta().getTag().size());
assertEquals("urn:foo", p.getMeta().getTag().get(0).getSystem());
assertEquals("bar", p.getMeta().getTag().get(0).getCode());
assertEquals("baz", p.getMeta().getTag().get(0).getDisplay());
}
@Test
public void testUpdateWithNoTagsNoHeader() {
Patient p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = new Patient();
p.setId("A");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
assertEquals("1", p.getIdElement().getVersionIdPart());
assertEquals(true, p.getActive());
assertEquals(1, p.getMeta().getTag().size());
assertEquals("urn:foo", p.getMeta().getTag().get(0).getSystem());
assertEquals("bar", p.getMeta().getTag().get(0).getCode());
assertEquals("baz", p.getMeta().getTag().get(0).getDisplay());
}
@Test
public void testUpdateWithNoTagsWithHeader() {
when(mySrd.getHeaders(eq(JpaConstants.HEADER_META_SNAPSHOT_MODE))).thenReturn(Lists.newArrayList("TAG"));
Patient p = new Patient();
p.setId("A");
p.getMeta().addTag("urn:foo", "bar", "baz");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = new Patient();
p.setId("A");
p.setActive(true);
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
assertEquals(true, p.getActive());
assertEquals(0, p.getMeta().getTag().size());
// It would be nice if this didn't trigger a version update but
// i guess it's not so bad that it does
assertEquals("2", p.getIdElement().getVersionIdPart());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -37,6 +37,7 @@ import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.Matchers.*;
@ -169,6 +170,24 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
return null;
}
@Test
public void testResourceCounts() {
Patient p = new Patient();
p.setActive(true);
myPatientDao.create(p);
Observation o = new Observation();
o.setStatus(ObservationStatus.AMENDED);
myObservationDao.create(o);
Map<String, Long> counts = mySystemDao.getResourceCounts();
assertEquals(new Long(1L), counts.get("Patient"));
assertEquals(new Long(1L), counts.get("Observation"));
assertEquals(null, counts.get("Organization"));
}
@Test
public void testBatchCreateWithBadRead() {
Bundle request = new Bundle();

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.util.SingleItemLoadingCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.StrictErrorHandler;
@ -40,6 +41,7 @@ import org.springframework.web.servlet.DispatcherServlet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -61,6 +63,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested;
protected SingleItemLoadingCache<Map<String, Long>> ourResourceCountsCache;
public BaseResourceProviderR4Test() {
super();
@ -99,6 +102,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
ourRestServer.setServerConformanceProvider(confProvider);
ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class);
ourResourceCountsCache = (SingleItemLoadingCache<Map<String, Long>>) myAppCtx.getBean("myResourceCountsCache");
Server server = new Server(ourPort);

View File

@ -1,30 +1,31 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ExtensionConstants;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.util.TestUtil;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.*;
public class ServerR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerR4Test.class);
/**
* See #519
*/
@ -35,12 +36,12 @@ public class ServerR4Test extends BaseResourceProviderR4Test {
try {
ourLog.info(resp.toString());
assertEquals(200, resp.getStatusLine().getStatusCode());
String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(respString);
CapabilityStatement cs = myFhirCtx.newXmlParser().parseResource(CapabilityStatement.class, respString);
for (CapabilityStatementRestResourceComponent nextResource : cs.getRest().get(0).getResource()) {
ourLog.info("Testing resource: " + nextResource.getType());
Set<String> sps = new HashSet<String>();
@ -49,17 +50,70 @@ public class ServerR4Test extends BaseResourceProviderR4Test {
fail("Duplicate search parameter " + nextSp.getName() + " for resource " + nextResource.getType());
}
}
if (!sps.contains("_id")) {
fail("No search parameter _id for resource " + nextResource.getType());
}
}
}
} finally {
IOUtils.closeQuietly(resp.getEntity().getContent());
IOUtils.closeQuietly(resp.getEntity().getContent());
}
}
@Test
public void testMetadataIncludesResourceCounts() {
Patient p = new Patient();
p.setActive(true);
myClient.create().resource(p).execute();
/*
* Initial fetch after a clear should return
* no results
*/
ourResourceCountsCache.clear();
CapabilityStatement capabilityStatement = myClient
.capabilities()
.ofType(CapabilityStatement.class)
.execute();
Extension patientCountExt = capabilityStatement
.getRest()
.get(0)
.getResource()
.stream()
.filter(t -> t.getType().equals("Patient"))
.findFirst()
.orElseThrow(() -> new InternalErrorException("No patient"))
.getExtensionByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
assertNull(patientCountExt);
/*
* Now run a background pass (the update
* method is called by the scheduler normally)
*/
ourResourceCountsCache.update();
capabilityStatement = myClient
.capabilities()
.ofType(CapabilityStatement.class)
.execute();
patientCountExt = capabilityStatement
.getRest()
.get(0)
.getResource()
.stream()
.filter(t -> t.getType().equals("Patient"))
.findFirst()
.orElseThrow(() -> new InternalErrorException("No patient"))
.getExtensionByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
assertEquals("1", patientCountExt.getValueAsPrimitive().getValueAsString());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -0,0 +1,65 @@
package ca.uhn.fhir.jpa.util;
import org.hl7.fhir.dstu3.model.CapabilityStatement;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.*;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class SingleItemLoadingCacheTest {
@Mock
private Callable<CapabilityStatement> myFetcher;
@After
public void after() {
SingleItemLoadingCache.setNowForUnitTest(null);
}
@Before
public void before() throws Exception {
AtomicInteger id = new AtomicInteger();
when(myFetcher.call()).thenAnswer(t->{
CapabilityStatement retVal = new CapabilityStatement();
retVal.setId("" + id.incrementAndGet());
return retVal;
});
}
@Test
public void testCache() {
long start = System.currentTimeMillis();
SingleItemLoadingCache.setNowForUnitTest(start);
// Cache is initialized on startup
SingleItemLoadingCache<CapabilityStatement> cache = new SingleItemLoadingCache<>(myFetcher);
cache.setCacheMillis(500);
assertEquals(null, cache.get());
// Not time to update yet
cache.update();
assertEquals("1", cache.get().getId());
// Wait a bit, still not time to update
SingleItemLoadingCache.setNowForUnitTest(start + 400);
cache.update();
assertEquals("1", cache.get().getId());
// Wait a bit more and the cache is expired
SingleItemLoadingCache.setNowForUnitTest(start + 800);
cache.update();
assertEquals("2", cache.get().getId());
}
}

View File

@ -257,7 +257,11 @@ public abstract class BaseResource extends BaseElement implements IResource {
@Override
public IBaseMetaType setLastUpdated(Date theHeaderDateValue) {
ResourceMetadataKeyEnum.UPDATED.put(BaseResource.this, new InstantDt(theHeaderDateValue));
if (theHeaderDateValue == null) {
getResourceMetadata().remove(ResourceMetadataKeyEnum.UPDATED);
} else {
ResourceMetadataKeyEnum.UPDATED.put(BaseResource.this, new InstantDt(theHeaderDateValue));
}
return this;
}

View File

@ -669,14 +669,15 @@ public final class IdType extends UriType implements IPrimitiveType<String>, IId
* Creates a new instance of this ID which is identical, but refers to the
* specific version of this resource ID noted by theVersion.
*
* @param theVersion
* The actual version string, e.g. "1"
* @param theVersion The actual version string, e.g. "1". If theVersion is blank or null, returns the same as {@link #toVersionless()}}
* @return A new instance of IdType which is identical, but refers to the
* specific version of this resource ID noted by theVersion.
*/
@Override
public IdType withVersion(String theVersion) {
Validate.notBlank(theVersion, "Version may not be null or empty");
if (isBlank(theVersion)) {
return toVersionless();
}
if (isLocal() || isUrn()) {
return new IdType(getValueAsString());

View File

@ -1,12 +1,7 @@
package ca.uhn.fhir.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Reference;
@ -14,82 +9,19 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import java.math.BigDecimal;
import static org.junit.Assert.*;
public class IdTypeDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(IdTypeDstu3Test.class);
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(IdTypeDstu3Test.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testUuid() {
IdType id = new IdType("urn:uuid:1234-5678");
assertEquals("urn:uuid:1234-5678", id.getValueAsString());
assertEquals("urn:uuid:1234-5678", id.getIdPart());
assertEquals("urn:uuid:1234-5678", id.toUnqualified().getValueAsString());
assertEquals("urn:uuid:1234-5678", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:uuid:1234-5678", id.withResourceType("Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withVersion("2").getValue());
}
@Test
public void testOid() {
IdType id = new IdType("urn:oid:1.2.3.4");
assertEquals("urn:oid:1.2.3.4", id.getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.getIdPart());
assertEquals("urn:oid:1.2.3.4", id.toUnqualified().getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:oid:1.2.3.4", id.withResourceType("Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withVersion("2").getValue());
}
@Test
public void testLocal() {
IdType id = new IdType("#foo");
assertEquals("#foo", id.getValueAsString());
assertEquals("#foo", id.getIdPart());
assertEquals("#foo", id.toUnqualified().getValueAsString());
assertEquals("#foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("#foo", id.withResourceType("Patient").getValue());
assertEquals("#foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("#foo", id.withVersion("2").getValue());
}
@Test
public void testNormal() {
IdType id = new IdType("foo");
assertEquals("foo", id.getValueAsString());
assertEquals("foo", id.getIdPart());
assertEquals("foo", id.toUnqualified().getValueAsString());
assertEquals("foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("Patient/foo", id.withResourceType("Patient").getValue());
assertEquals("http://foo/Patient/foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("foo/_history/2", id.withVersion("2").getValue());
private Patient parseAndEncode(Patient patient) {
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
ourLog.info("\n" + encoded);
return ourCtx.newXmlParser().parseResource(Patient.class, encoded);
}
@Test
@ -124,14 +56,52 @@ public class IdTypeDstu3Test {
assertEquals("http://my.org/a/b/c/foo/_history/2", id.withVersion("2").getValue());
}
@Test
public void testBigDecimalIds() {
IdType id = new IdType(new BigDecimal("123"));
assertEquals(id.getIdPartAsBigDecimal(), new BigDecimal("123"));
}
/**
* See #67
*/
@Test
public void testComplicatedLocal() {
IdType id = new IdType("#Patient/cid:Patient-72/_history/1");
assertTrue(id.isLocal());
assertEquals(null, id.getBaseUrl());
assertNull(id.getResourceType());
assertNull(id.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id.getIdPart());
IdType id2 = new IdType("#Patient/cid:Patient-72/_history/1");
assertEquals(id, id2);
id2 = id2.toUnqualified();
assertTrue(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart());
}
@Test
public void testConstructorsWithNullArguments() {
IdType id = new IdType(null, null, null);
assertEquals(null, id.getValue());
}
@Test
public void testDetectLocal() {
IdType id;
id = new IdType("#123");
assertEquals("#123", id.getValue());
assertTrue(id.isLocal());
id = new IdType("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1");
assertEquals("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1", id.getValue());
assertTrue(id.isLocal());
@ -140,12 +110,6 @@ public class IdTypeDstu3Test {
assertEquals("http://example.com/Patient/33#123", id.getValue());
assertFalse(id.isLocal());
}
@Test
public void testConstructorsWithNullArguments() {
IdType id = new IdType(null, null, null);
assertEquals(null, id.getValue());
}
@Test
public void testDetectLocalBase() {
@ -161,32 +125,7 @@ public class IdTypeDstu3Test {
assertEquals(null, new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("#180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
}
/**
* See #67
*/
@Test
public void testComplicatedLocal() {
IdType id = new IdType("#Patient/cid:Patient-72/_history/1");
assertTrue(id.isLocal());
assertEquals(null, id.getBaseUrl());
assertNull(id.getResourceType());
assertNull(id.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id.getIdPart());
IdType id2 = new IdType("#Patient/cid:Patient-72/_history/1");
assertEquals(id, id2);
id2 = id2.toUnqualified();
assertTrue(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart());
}
@Test
public void testDetermineBase() {
@ -197,12 +136,60 @@ public class IdTypeDstu3Test {
rr = new IdType("http://foo/fhir/Organization/123/_history/123");
assertEquals("http://foo/fhir", rr.getBaseUrl());
rr = new IdType("Organization/123/_history/123");
assertEquals(null, rr.getBaseUrl());
}
@Test
public void testLocal() {
IdType id = new IdType("#foo");
assertEquals("#foo", id.getValueAsString());
assertEquals("#foo", id.getIdPart());
assertEquals("#foo", id.toUnqualified().getValueAsString());
assertEquals("#foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("#foo", id.withResourceType("Patient").getValue());
assertEquals("#foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("#foo", id.withVersion("2").getValue());
}
@Test
public void testNormal() {
IdType id = new IdType("foo");
assertEquals("foo", id.getValueAsString());
assertEquals("foo", id.getIdPart());
assertEquals("foo", id.toUnqualified().getValueAsString());
assertEquals("foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("Patient/foo", id.withResourceType("Patient").getValue());
assertEquals("http://foo/Patient/foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("foo/_history/2", id.withVersion("2").getValue());
}
@Test
public void testOid() {
IdType id = new IdType("urn:oid:1.2.3.4");
assertEquals("urn:oid:1.2.3.4", id.getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.getIdPart());
assertEquals("urn:oid:1.2.3.4", id.toUnqualified().getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:oid:1.2.3.4", id.withResourceType("Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withVersion("2").getValue());
}
@Test
public void testParseValueAbsolute() {
Patient patient = new Patient();
@ -218,14 +205,6 @@ public class IdTypeDstu3Test {
}
@Test
public void testBigDecimalIds() {
IdType id = new IdType(new BigDecimal("123"));
assertEquals(id.getIdPartAsBigDecimal(), new BigDecimal("123"));
}
@Test
public void testParseValueAbsoluteWithVersion() {
Patient patient = new Patient();
@ -241,30 +220,6 @@ public class IdTypeDstu3Test {
}
@Test
public void testViewMethods() {
IdType i = new IdType("http://foo/fhir/Organization/123/_history/999");
assertEquals("Organization/123/_history/999", i.toUnqualified().getValue());
assertEquals("http://foo/fhir/Organization/123", i.toVersionless().getValue());
assertEquals("Organization/123", i.toUnqualifiedVersionless().getValue());
}
@Test
public void testParseValueWithVersion() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("/123/_history/999");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals(null, ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
assertEquals(null, ref.getReferenceElement().getVersionIdPart());
}
@Test
public void testParseValueMissingType1() {
Patient patient = new Patient();
@ -321,10 +276,53 @@ public class IdTypeDstu3Test {
}
private Patient parseAndEncode(Patient patient) {
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
ourLog.info("\n" + encoded);
return ourCtx.newXmlParser().parseResource(Patient.class, encoded);
@Test
public void testParseValueWithVersion() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("/123/_history/999");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals(null, ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
assertEquals(null, ref.getReferenceElement().getVersionIdPart());
}
@Test
public void testUuid() {
IdType id = new IdType("urn:uuid:1234-5678");
assertEquals("urn:uuid:1234-5678", id.getValueAsString());
assertEquals("urn:uuid:1234-5678", id.getIdPart());
assertEquals("urn:uuid:1234-5678", id.toUnqualified().getValueAsString());
assertEquals("urn:uuid:1234-5678", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:uuid:1234-5678", id.withResourceType("Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withVersion("2").getValue());
}
@Test
public void testViewMethods() {
IdType i = new IdType("http://foo/fhir/Organization/123/_history/999");
assertEquals("Organization/123/_history/999", i.toUnqualified().getValue());
assertEquals("http://foo/fhir/Organization/123", i.toVersionless().getValue());
assertEquals("Organization/123", i.toUnqualifiedVersionless().getValue());
}
@Test
public void testWithVersionNull() {
assertEquals("Patient/123", new IdType("Patient/123/_history/2").withVersion("").getValue());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass

View File

@ -709,13 +709,15 @@ public final class IdType extends UriType implements IPrimitiveType<String>, IId
* Creates a new instance of this ID which is identical, but refers to the
* specific version of this resource ID noted by theVersion.
*
* @param theVersion The actual version string, e.g. "1"
* @param theVersion The actual version string, e.g. "1". If theVersion is blank or null, returns the same as {@link #toVersionless()}}
* @return A new instance of IdType which is identical, but refers to the
* specific version of this resource ID noted by theVersion.
*/
@Override
public IdType withVersion(String theVersion) {
Validate.notBlank(theVersion, "Version may not be null or empty");
if (isBlank(theVersion)) {
return toVersionless();
}
if (isLocal() || isUrn()) {
return new IdType(getValueAsString());

View File

@ -12,13 +12,13 @@ import static org.junit.Assert.*;
public class IdTypeR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(IdTypeR4Test.class);
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(IdTypeR4Test.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
private Patient parseAndEncode(Patient patient) {
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
ourLog.info("\n" + encoded);
return ourCtx.newXmlParser().parseResource(Patient.class, encoded);
}
@Test
@ -53,79 +53,52 @@ public class IdTypeR4Test {
assertEquals("http://my.org/a/b/c/foo/_history/2", id.withVersion("2").getValue());
}
@Test
public void testBigDecimalIds() {
IdType id = new IdType(new BigDecimal("123"));
assertEquals(id.getIdPartAsBigDecimal(), new BigDecimal("123"));
@Test
public void testUuid() {
IdType id = new IdType("urn:uuid:1234-5678");
assertEquals("urn:uuid:1234-5678", id.getValueAsString());
assertEquals("urn:uuid:1234-5678", id.getIdPart());
assertEquals("urn:uuid:1234-5678", id.toUnqualified().getValueAsString());
assertEquals("urn:uuid:1234-5678", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:uuid:1234-5678", id.withResourceType("Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withVersion("2").getValue());
}
/**
* See #67
*/
@Test
public void testOid() {
IdType id = new IdType("urn:oid:1.2.3.4");
assertEquals("urn:oid:1.2.3.4", id.getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.getIdPart());
assertEquals("urn:oid:1.2.3.4", id.toUnqualified().getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
public void testComplicatedLocal() {
IdType id = new IdType("#Patient/cid:Patient-72/_history/1");
assertTrue(id.isLocal());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:oid:1.2.3.4", id.withResourceType("Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withVersion("2").getValue());
assertNull(id.getResourceType());
assertNull(id.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id.getIdPart());
IdType id2 = new IdType("#Patient/cid:Patient-72/_history/1");
assertEquals(id, id2);
id2 = id2.toUnqualified();
assertTrue(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart());
}
@Test
public void testLocal() {
IdType id = new IdType("#foo");
assertEquals("#foo", id.getValueAsString());
assertEquals("#foo", id.getIdPart());
assertEquals("#foo", id.toUnqualified().getValueAsString());
assertEquals("#foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("#foo", id.withResourceType("Patient").getValue());
assertEquals("#foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("#foo", id.withVersion("2").getValue());
}
@Test
public void testNormal() {
IdType id = new IdType("foo");
assertEquals("foo", id.getValueAsString());
assertEquals("foo", id.getIdPart());
assertEquals("foo", id.toUnqualified().getValueAsString());
assertEquals("foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("Patient/foo", id.withResourceType("Patient").getValue());
assertEquals("http://foo/Patient/foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("foo/_history/2", id.withVersion("2").getValue());
public void testConstructorsWithNullArguments() {
IdType id = new IdType(null, null, null);
assertEquals(null, id.getValue());
}
@Test
public void testDetectLocal() {
IdType id;
id = new IdType("#123");
assertEquals("#123", id.getValue());
assertTrue(id.isLocal());
id = new IdType("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1");
assertEquals("#Medication/499059CE-CDD4-48BC-9014-528A35D15CED/_history/1", id.getValue());
assertTrue(id.isLocal());
@ -134,12 +107,6 @@ public class IdTypeR4Test {
assertEquals("http://example.com/Patient/33#123", id.getValue());
assertFalse(id.isLocal());
}
@Test
public void testConstructorsWithNullArguments() {
IdType id = new IdType(null, null, null);
assertEquals(null, id.getValue());
}
@Test
public void testDetectLocalBase() {
@ -155,32 +122,7 @@ public class IdTypeR4Test {
assertEquals(null, new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getBaseUrl());
assertEquals("#180f219f-97a8-486d-99d9-ed631fe4fc57", new IdType("#180f219f-97a8-486d-99d9-ed631fe4fc57").getIdPart());
}
/**
* See #67
*/
@Test
public void testComplicatedLocal() {
IdType id = new IdType("#Patient/cid:Patient-72/_history/1");
assertTrue(id.isLocal());
assertEquals(null, id.getBaseUrl());
assertNull(id.getResourceType());
assertNull(id.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id.getIdPart());
IdType id2 = new IdType("#Patient/cid:Patient-72/_history/1");
assertEquals(id, id2);
id2 = id2.toUnqualified();
assertTrue(id2.isLocal());
assertNull(id2.getBaseUrl());
assertNull(id2.getResourceType());
assertNull(id2.getVersionIdPart());
assertEquals("#Patient/cid:Patient-72/_history/1", id2.getIdPart());
}
@Test
public void testDetermineBase() {
@ -191,12 +133,67 @@ public class IdTypeR4Test {
rr = new IdType("http://foo/fhir/Organization/123/_history/123");
assertEquals("http://foo/fhir", rr.getBaseUrl());
rr = new IdType("Organization/123/_history/123");
assertEquals(null, rr.getBaseUrl());
}
@Test
public void testEncodeParts() {
IdType id = new IdType("http://foo", "Patient", "123", "456");
assertEquals("http://foo/Patient/123/_history/456", id.getValue());
assertEquals("http://foo/Patient/123/_history/9", id.withVersion("9").getValue());
}
@Test
public void testLocal() {
IdType id = new IdType("#foo");
assertEquals("#foo", id.getValueAsString());
assertEquals("#foo", id.getIdPart());
assertEquals("#foo", id.toUnqualified().getValueAsString());
assertEquals("#foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("#foo", id.withResourceType("Patient").getValue());
assertEquals("#foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("#foo", id.withVersion("2").getValue());
}
@Test
public void testNormal() {
IdType id = new IdType("foo");
assertEquals("foo", id.getValueAsString());
assertEquals("foo", id.getIdPart());
assertEquals("foo", id.toUnqualified().getValueAsString());
assertEquals("foo", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("Patient/foo", id.withResourceType("Patient").getValue());
assertEquals("http://foo/Patient/foo", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("foo/_history/2", id.withVersion("2").getValue());
}
@Test
public void testOid() {
IdType id = new IdType("urn:oid:1.2.3.4");
assertEquals("urn:oid:1.2.3.4", id.getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.getIdPart());
assertEquals("urn:oid:1.2.3.4", id.toUnqualified().getValueAsString());
assertEquals("urn:oid:1.2.3.4", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:oid:1.2.3.4", id.withResourceType("Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:oid:1.2.3.4", id.withVersion("2").getValue());
}
@Test
public void testParseValueAbsolute() {
Patient patient = new Patient();
@ -212,14 +209,6 @@ public class IdTypeR4Test {
}
@Test
public void testBigDecimalIds() {
IdType id = new IdType(new BigDecimal("123"));
assertEquals(id.getIdPartAsBigDecimal(), new BigDecimal("123"));
}
@Test
public void testParseValueAbsoluteWithVersion() {
Patient patient = new Patient();
@ -235,30 +224,6 @@ public class IdTypeR4Test {
}
@Test
public void testViewMethods() {
IdType i = new IdType("http://foo/fhir/Organization/123/_history/999");
assertEquals("Organization/123/_history/999", i.toUnqualified().getValue());
assertEquals("http://foo/fhir/Organization/123", i.toVersionless().getValue());
assertEquals("Organization/123", i.toUnqualifiedVersionless().getValue());
}
@Test
public void testParseValueWithVersion() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("/123/_history/999");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals(null, ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
assertEquals(null, ref.getReferenceElement().getVersionIdPart());
}
@Test
public void testParseValueMissingType1() {
Patient patient = new Patient();
@ -301,13 +266,6 @@ public class IdTypeR4Test {
}
@Test
public void testEncodeParts() {
IdType id = new IdType("http://foo", "Patient", "123", "456");
assertEquals("http://foo/Patient/123/_history/456", id.getValue());
assertEquals("http://foo/Patient/123/_history/9", id.withVersion("9").getValue());
}
@Test
public void testParseValueRelative2() {
Patient patient = new Patient();
@ -322,10 +280,53 @@ public class IdTypeR4Test {
}
private Patient parseAndEncode(Patient patient) {
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
ourLog.info("\n" + encoded);
return ourCtx.newXmlParser().parseResource(Patient.class, encoded);
@Test
public void testParseValueWithVersion() {
Patient patient = new Patient();
IdType rr = new IdType();
rr.setValue("/123/_history/999");
patient.setManagingOrganization(new Reference(rr));
Patient actual = parseAndEncode(patient);
Reference ref = actual.getManagingOrganization();
assertEquals(null, ref.getReferenceElement().getResourceType());
assertEquals("123", ref.getReferenceElement().getIdPart());
assertEquals(null, ref.getReferenceElement().getVersionIdPart());
}
@Test
public void testUuid() {
IdType id = new IdType("urn:uuid:1234-5678");
assertEquals("urn:uuid:1234-5678", id.getValueAsString());
assertEquals("urn:uuid:1234-5678", id.getIdPart());
assertEquals("urn:uuid:1234-5678", id.toUnqualified().getValueAsString());
assertEquals("urn:uuid:1234-5678", id.toUnqualifiedVersionless().getValueAsString());
assertEquals(null, id.getVersionIdPart());
assertEquals(null, id.getResourceType());
assertEquals(null, id.getBaseUrl());
assertEquals("urn:uuid:1234-5678", id.withResourceType("Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withServerBase("http://foo", "Patient").getValue());
assertEquals("urn:uuid:1234-5678", id.withVersion("2").getValue());
}
@Test
public void testViewMethods() {
IdType i = new IdType("http://foo/fhir/Organization/123/_history/999");
assertEquals("Organization/123/_history/999", i.toUnqualified().getValue());
assertEquals("http://foo/fhir/Organization/123", i.toVersionless().getValue());
assertEquals("Organization/123", i.toUnqualifiedVersionless().getValue());
}
@Test
public void testWithVersionNull() {
assertEquals("Patient/123", new IdType("Patient/123/_history/2").withVersion("").getValue());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass

View File

@ -78,6 +78,37 @@
applies to the operation by name whether it is at the
server, type, or instance level.
</action>
<action type="add">
Calling <![CDATA[<code>IdType#withVersion(String)</code>]]>
with a null/blank parameter will now return a copy of the
ID with the version removed. Previously this call would
deliberately cause an IllegalArgumentException.
</action>
<action type="fix">
When updating resources on the JPA server, tags did not always
consistently follow FHIR's recommended rules for tag retention. According
to FHIR's rules, if a tag is not explicitly present on an update but
was present on the previous version, it should be carried forward anyhow.
Due to a bug, this happened when more than one tag was present
but not when only one was present. This has been corrected. In
addition, a new request header called
<![CDATA[<code>X-Meta-Snapshot-Mode</code>]]>
has been added that can be used by the client to override
this behaviour.
</action>
<action type="fix">
The JPA server's resource counts query has been optimized to
give the database a bit more flexibility to
optimize, which should increase performance for this query.
</action>
<action type="add">
The JPA server CapabilityStatement generator has been tuned
so that resource counts are no longer calculated synchronously
as a part of building the CapabilityStatement response. With
this change, counts are calculated in the background and cached
which can yield significant performance improvements on
hevaily loaded servers.
</action>
</release>
<release version="3.3.0" date="2018-03-29">
<action type="add">