diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java index 869de46d65d..5feb2832010 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java @@ -619,12 +619,14 @@ public class IdDt extends UriDt implements /*IPrimitiveDatatype, */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()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index e9b6af6a91f..4acb8b0e0d5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -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> resourceCountsCache() { + SingleItemLoadingCache> 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(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index 807512adef5..6a928ee3063 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -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> resourceCountsCache() { + SingleItemLoadingCache> 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(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java index 723fd56e949..2e5cd84b262 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java @@ -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> resourceCountsCache() { + SingleItemLoadingCache> 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(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 5ae209eaae2..08b5727f209 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -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 implements IDao { return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource); } - private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set allDefs) { + private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set 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 implements IDao { List 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 implements IDao { List 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 allDefs) { + private void extractTagsRi(IAnyResource theResource, ResourceTable theEntity, Set theAllTags) { List 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 implements IDao { List 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 implements IDao { List> profiles = theResource.getMeta().getProfile(); if (profiles != null) { for (IPrimitiveType 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 implements IDao { ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount); } - private Set getAllTagDefinitions(ResourceTable theEntity) { - HashSet retVal = Sets.newHashSet(); - for (ResourceTag next : theEntity.getTags()) { - retVal.add(next.getTag()); + private Set getAllTagDefinitions(ResourceTable theEntity) { + HashSet retVal = Sets.newHashSet(); + if (theEntity.isHasTags()) { + for (ResourceTag next : theEntity.getTags()) { + retVal.add(next); + } } return retVal; } @@ -1173,7 +1179,7 @@ public abstract class BaseHapiFhirDao 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 implements IDao { theEntity.setHashSha256(hashSha256); } - Set allDefs = new HashSet<>(); - - theEntity.setHasTags(false); - - Set allTagsOld = getAllTagDefinitions(theEntity); + Set allDefs = new HashSet<>(); + Set allTagsOld = getAllTagDefinitions(theEntity); if (theResource instanceof IResource) { extractTagsHapi((IResource) theResource, theEntity, allDefs); @@ -1238,32 +1241,33 @@ public abstract class BaseHapiFhirDao 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 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 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 allTagsNew = getAllTagDefinitions(theEntity); + Set 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 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. *

*

- * See Updates to Tags, Profiles, and Security Labels for a description of the logic that the default behaviour folows. + * See Updates to Tags, Profiles, and Security Labels for a description of the logic that the default behaviour folows. *

* - * @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 true 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 metaSnapshotModeTokens = null; + + if (theRequest != null) { + List 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 implements IDao { @SuppressWarnings("unchecked") @Override - public R toResource(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) { + public R toResource(Class theResourceType, BaseHasResource theEntity, + boolean theForHistoryOperation) { ResourceHistoryTable history; if (theEntity instanceof ResourceHistoryTable) { @@ -1639,7 +1674,8 @@ public abstract class BaseHapiFhirDao 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 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 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 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 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 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 implements IDao { * @param theResourceType E.g. Patient * @param thePartsChoices E.g. [[gender=male], [name=SMITH, name=JOHN]] */ - public static Set extractCompositeStringUniquesValueChains(String theResourceType, List> thePartsChoices) { + public static Set extractCompositeStringUniquesValueChains(String + theResourceType, List> thePartsChoices) { for (List next : thePartsChoices) { - for (Iterator 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 implements IDao { return Collections.emptySet(); } - Collections.sort(thePartsChoices, new Comparator>() { - @Override - public int compare(List o1, List 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 values = new ArrayList<>(); @@ -2309,7 +2342,8 @@ public abstract class BaseHapiFhirDao implements IDao { return queryStringsToPopulate; } - private static void extractCompositeStringUniquesValueChains(String theResourceType, List> thePartsChoices, List theValues, Set theQueryStringsToPopulate) { + private static void extractCompositeStringUniquesValueChains(String + theResourceType, List> thePartsChoices, List theValues, Set theQueryStringsToPopulate) { if (thePartsChoices.size() > 0) { List nextList = thePartsChoices.get(0); Collections.sort(nextList); @@ -2446,14 +2480,15 @@ public abstract class BaseHapiFhirDao implements IDao { } private static List toBaseCodingList(List theSecurityLabels) { - ArrayList retVal = new ArrayList(theSecurityLabels.size()); + ArrayList 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 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 parameters = translateMatchUrl(theMatchUrl); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 1553d97c20f..0d839c8dc85 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -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 extends B } @Override - public DaoMethodOutcome delete(IIdType theId, List theDeleteConflicts, RequestDetails theRequestDetails) { + public DaoMethodOutcome delete(IIdType theId, List 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 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 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 extends B * transaction processors */ @Override - public DeleteMethodOutcome deleteByUrl(String theUrl, List deleteConflicts, RequestDetails theRequestDetails) { + public DeleteMethodOutcome deleteByUrl(String theUrl, List deleteConflicts, RequestDetails theRequest) { StopWatch w = new StopWatch(); Set resource = processMatchUrl(theUrl, myResourceType); @@ -290,12 +291,12 @@ public abstract class BaseHapiFhirResourceDao 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 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 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 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 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 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 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 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) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index 0fc0f611737..6b1e936532d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -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 extends BaseHapiFhirDao> myResourceCountsCache; + private int doPerformReindexingPass(final Integer theCount) { /* @@ -165,23 +165,23 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao getResourceCounts() { - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery 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 q = myEntityManager.createQuery(cq); - Map 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> 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 getResourceCountsFromCache() { + return myResourceCountsCache.get(); + } + @Override public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) { if (theRequestDetails != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java index d0c5d73ac40..72b44e1b251 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java @@ -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 implements IFhirResourceDaoSubscription { - 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 { 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); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java index 401ae09689c..2a1e88a81af 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java @@ -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 extends IDao { Map getResourceCounts(); + /** + *Returns a cached count of resources using a cache that regularly + * refreshes in the background. This method will never + */ + @Nullable + Map getResourceCountsFromCache(); + + IBundleProvider history(Date theDate, Date theUntil, RequestDetails theRequestDetails); /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java index c396a24fb57..cd9f23f01b4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java @@ -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 { @Query("SELECT t.myId FROM ResourceTable t WHERE t.myIndexStatus IS NULL") Slice findUnindexed(Pageable thePageRequest); + @Query("SELECT t.myResourceType as type, COUNT(*) as count FROM ResourceTable t GROUP BY t.myResourceType") + List> getResourceCounts(); + @Modifying @Query("UPDATE ResourceTable r SET r.myIndexStatus = null WHERE r.myResourceType = :restype") int markResourcesOfTypeAsRequiringReindexing(@Param("restype") String theResourceType); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 8bc6722ee72..29cfecb2940 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -199,9 +199,9 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 { 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); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java index 030683b562e..0f3bd4cdec8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java @@ -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 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; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java index 8fb7349189e..98ecc91d3e9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java @@ -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 { } @SuppressWarnings("unchecked") - private Map doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set theAllIds, + private Map doTransactionWriteOperations(RequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set theAllIds, Map theIdSubstitutions, Map theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap theOriginalRequestOrder, List theEntries) { Set deletedResources = new HashSet<>(); List deleteConflicts = new ArrayList<>(); @@ -545,9 +544,9 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao { 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 { } private static void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map 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) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java index 011225222cb..92f53cd1308 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java @@ -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 mySystemDao; - - /** - * Constructor - */ - @CoverageIgnore - public JpaConformanceProviderDstu2(){ - super(); - super.setCache(false); - setIncludeResourceCounts(true); - } + private SingleItemLoadingCache> myResourceCountsCache; /** * Constructor @@ -79,10 +73,11 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider { public Conformance getServerConformance(HttpServletRequest theRequest) { Conformance retVal = myCachedValue; - Map counts = Collections.emptyMap(); + Map counts = null; if (myIncludeResourceCounts) { - counts = mySystemDao.getResourceCounts(); + counts = mySystemDao.getResourceCountsFromCache(); } + counts = defaultIfNull(counts, Collections.emptyMap()); FhirContext ctx = myRestfulServer.getFhirContext(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java index 009bd14a0d4..770aa01c73f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java @@ -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 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 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); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java index 1b608546029..87828c70b0a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java @@ -187,8 +187,8 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus counts = mySystemDao.getResourceCounts(); - counts = new TreeMap(counts); + Map counts = mySystemDao.getResourceCountsFromCache(); + counts = new TreeMap<>(counts); for (Entry nextEntry : counts.entrySet()) { retVal.addParameter().setName(new StringDt(nextEntry.getKey())).setValue(new IntegerDt(nextEntry.getValue().intValue())); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java index 8869894c492..8f2962d182e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java @@ -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 counts = Collections.emptyMap(); + Map counts = null; if (myIncludeResourceCounts) { - counts = mySystemDao.getResourceCounts(); + counts = mySystemDao.getResourceCountsFromCache(); } + counts = defaultIfNull(counts, Collections.emptyMap()); retVal = super.getServerConformance(theRequest); for (CapabilityStatementRestComponent nextRest : retVal.getRest()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java index 384a3185250..0248d74521e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java @@ -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 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 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 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 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'"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java index dcc97567391..035a5f4265b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java @@ -191,8 +191,8 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus counts = mySystemDao.getResourceCounts(); - counts = new TreeMap(counts); + Map counts = mySystemDao.getResourceCountsFromCache(); + counts = new TreeMap<>(counts); for (Entry nextEntry : counts.entrySet()) { retVal.addParameter().setName((nextEntry.getKey())).setValue(new IntegerType(nextEntry.getValue().intValue())); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java index a145e11880d..0bf42f6cb5e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java @@ -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 counts = Collections.emptyMap(); + Map counts = null; if (myIncludeResourceCounts) { - counts = mySystemDao.getResourceCounts(); + counts = mySystemDao.getResourceCountsFromCache(); } + counts = defaultIfNull(counts, Collections.emptyMap()); retVal = super.getServerConformance(theRequest); for (CapabilityStatementRestComponent nextRest : retVal.getRest()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java index c4b0064e969..99351665854 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java @@ -177,8 +177,8 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus counts = mySystemDao.getResourceCounts(); - counts = new TreeMap(counts); + Map counts = mySystemDao.getResourceCountsFromCache(); + counts = new TreeMap<>(counts); for (Entry nextEntry : counts.entrySet()) { retVal.addParameter().setName((nextEntry.getKey())).setValue(new IntegerType(nextEntry.getValue().intValue())); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java index 9883fd242cd..3363844c109 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java @@ -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"; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SingleItemLoadingCache.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SingleItemLoadingCache.java new file mode 100644 index 00000000000..f8b0ec6981a --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SingleItemLoadingCache.java @@ -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 { + private static final Logger ourLog = LoggerFactory.getLogger(SingleItemLoadingCache.class); + private static Long ourNowForUnitTest; + private final Callable myFetcher; + private volatile long myCacheMillis; + private AtomicReference myCapabilityStatement = new AtomicReference<>(); + private long myLastFetched; + + /** + * Constructor + */ + public SingleItemLoadingCache(Callable 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; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SpringObjectCaster.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SpringObjectCaster.java deleted file mode 100644 index 90483f46a30..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SpringObjectCaster.java +++ /dev/null @@ -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 - * @return - * @throws Exception - */ - public static T getTargetObject(Object proxy, Class clazz) throws Exception { - while( (AopUtils.isJdkDynamicProxy(proxy))) { - return clazz.cast(getTargetObject(((Advised)proxy).getTargetSource().getTarget(), clazz)); - } - - return clazz.cast(proxy); - } -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTagSnapshotTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTagSnapshotTest.java new file mode 100644 index 00000000000..632dd8bfa86 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTagSnapshotTest.java @@ -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(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java index 4391e833786..33e9a4c5d71 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java @@ -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 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(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java index 12e8f7f0967..81e322d42db 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java @@ -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> ourResourceCountsCache; public BaseResourceProviderR4Test() { super(); @@ -99,6 +102,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { ourRestServer.setServerConformanceProvider(confProvider); ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class); + ourResourceCountsCache = (SingleItemLoadingCache>) myAppCtx.getBean("myResourceCountsCache"); Server server = new Server(ourPort); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java index 65402320ee2..ca9766e73f3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java @@ -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 sps = new HashSet(); @@ -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(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/SingleItemLoadingCacheTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/SingleItemLoadingCacheTest.java new file mode 100644 index 00000000000..09f8fcbc7c3 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/SingleItemLoadingCacheTest.java @@ -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 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 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()); + + } + +} diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java index 5dfb3219c1d..588a8311f8b 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java @@ -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; } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/IdType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/IdType.java index be8e1ab57a0..43ae2d54979 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/IdType.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/IdType.java @@ -669,14 +669,15 @@ public final class IdType extends UriType implements IPrimitiveType, 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()); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/model/IdTypeDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/model/IdTypeDstu3Test.java index 330e0dba256..30e2ae0b601 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/model/IdTypeDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/model/IdTypeDstu3Test.java @@ -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 diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/IdType.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/IdType.java index d7b66decb3f..292d6556e96 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/IdType.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/model/IdType.java @@ -709,13 +709,15 @@ public final class IdType extends UriType implements IPrimitiveType, 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()); diff --git a/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/model/IdTypeR4Test.java b/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/model/IdTypeR4Test.java index 73300adc607..85d493100b1 100644 --- a/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/model/IdTypeR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/model/IdTypeR4Test.java @@ -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 diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c5acecfd0bc..a42f71ccc2b 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -78,6 +78,37 @@ applies to the operation by name whether it is at the server, type, or instance level. + + Calling IdType#withVersion(String)]]> + 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. + + + 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 + X-Meta-Snapshot-Mode]]> + has been added that can be used by the client to override + this behaviour. + + + 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. + + + 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. +