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}