001/* 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.dao; 021 022import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; 023import ca.uhn.fhir.jpa.dao.validation.SearchParameterDaoValidator; 024import ca.uhn.fhir.jpa.model.entity.ResourceTable; 025import ca.uhn.fhir.rest.api.server.RequestDetails; 026import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; 027import com.google.common.annotations.VisibleForTesting; 028import org.hl7.fhir.instance.model.api.IBaseResource; 029import org.hl7.fhir.r5.model.Enumeration; 030import org.springframework.beans.factory.annotation.Autowired; 031import org.springframework.transaction.annotation.Propagation; 032import org.springframework.transaction.support.TransactionSynchronization; 033import org.springframework.transaction.support.TransactionSynchronizationManager; 034 035import java.util.List; 036import java.util.concurrent.atomic.AtomicBoolean; 037import java.util.stream.Collectors; 038 039public class JpaResourceDaoSearchParameter<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> 040 implements IFhirResourceDaoSearchParameter<T> { 041 042 private final AtomicBoolean myCacheReloadTriggered = new AtomicBoolean(false); 043 044 @Autowired 045 private VersionCanonicalizer myVersionCanonicalizer; 046 047 @Autowired 048 private SearchParameterDaoValidator mySearchParameterDaoValidator; 049 050 protected void reindexAffectedResources(T theResource, RequestDetails theRequestDetails) { 051 052 /* 053 * After we commit, flush the search parameter cache. This only helps on the 054 * local server (ie in a cluster the other servers won't be flushed) but 055 * the cache is short anyhow, and flushing locally is better than nothing. 056 * Many use cases where you would create a search parameter and immediately 057 * try to use it tend to be on single-server setups anyhow, e.g. unit tests 058 */ 059 if (!shouldSkipReindex(theRequestDetails)) { 060 if (!myCacheReloadTriggered.getAndSet(true)) { 061 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { 062 @Override 063 public void afterCommit() { 064 myTransactionService 065 .withSystemRequest() 066 .withPropagation(Propagation.NOT_SUPPORTED) 067 .execute(() -> { 068 // do this outside any current tx. 069 myCacheReloadTriggered.set(false); 070 mySearchParamRegistry.forceRefresh(); 071 }); 072 } 073 }); 074 } 075 } 076 077 // N.B. Don't do this on the canonicalized version 078 Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null; 079 080 org.hl7.fhir.r5.model.SearchParameter searchParameter = 081 myVersionCanonicalizer.searchParameterToCanonical(theResource); 082 List<String> base = theResource != null 083 ? searchParameter.getBase().stream().map(Enumeration::getCode).collect(Collectors.toList()) 084 : null; 085 requestReindexForRelatedResources(reindex, base, theRequestDetails); 086 } 087 088 @Override 089 protected void postPersist(ResourceTable theEntity, T theResource, RequestDetails theRequestDetails) { 090 super.postPersist(theEntity, theResource, theRequestDetails); 091 reindexAffectedResources(theResource, theRequestDetails); 092 } 093 094 @Override 095 protected void postUpdate(ResourceTable theEntity, T theResource, RequestDetails theRequestDetails) { 096 super.postUpdate(theEntity, theResource, theRequestDetails); 097 reindexAffectedResources(theResource, theRequestDetails); 098 } 099 100 @Override 101 protected void preDelete(T theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) { 102 super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails); 103 reindexAffectedResources(theResourceToDelete, theRequestDetails); 104 } 105 106 @Override 107 protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) { 108 super.validateResourceForStorage(theResource, theEntityToSave); 109 110 validateSearchParam(theResource); 111 } 112 113 public void validateSearchParam(IBaseResource theResource) { 114 org.hl7.fhir.r5.model.SearchParameter searchParameter = 115 myVersionCanonicalizer.searchParameterToCanonical(theResource); 116 mySearchParameterDaoValidator.validate(searchParameter); 117 } 118 119 @VisibleForTesting 120 void setVersionCanonicalizerForUnitTest(VersionCanonicalizer theVersionCanonicalizer) { 121 myVersionCanonicalizer = theVersionCanonicalizer; 122 } 123 124 @VisibleForTesting 125 public void setSearchParameterDaoValidatorForUnitTest(SearchParameterDaoValidator theSearchParameterDaoValidator) { 126 mySearchParameterDaoValidator = theSearchParameterDaoValidator; 127 } 128}