001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2023 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.search;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.RuntimeResourceDefinition;
024import ca.uhn.fhir.jpa.dao.data.IResourceSearchUrlDao;
025import ca.uhn.fhir.jpa.model.dao.JpaPid;
026import ca.uhn.fhir.jpa.model.entity.ResourceSearchUrlEntity;
027import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
028import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031import org.springframework.stereotype.Service;
032import org.springframework.transaction.annotation.Transactional;
033
034import java.util.Date;
035import javax.persistence.EntityManager;
036
037/**
038 * This service ensures uniqueness of resources during create or create-on-update by storing the resource searchUrl.
039 */
040@Transactional
041@Service
042public class ResourceSearchUrlSvc {
043        private static final Logger ourLog = LoggerFactory.getLogger(ResourceSearchUrlSvc.class);
044        private final EntityManager myEntityManager;
045
046        private final IResourceSearchUrlDao myResourceSearchUrlDao;
047
048        private final MatchUrlService myMatchUrlService;
049
050        private final FhirContext myFhirContext;
051
052        public ResourceSearchUrlSvc(
053                        EntityManager theEntityManager,
054                        IResourceSearchUrlDao theResourceSearchUrlDao,
055                        MatchUrlService theMatchUrlService,
056                        FhirContext theFhirContext) {
057                myEntityManager = theEntityManager;
058                myResourceSearchUrlDao = theResourceSearchUrlDao;
059                myMatchUrlService = theMatchUrlService;
060                myFhirContext = theFhirContext;
061        }
062
063        /**
064         * Perform removal of entries older than {@code theCutoffDate} since the create operations are done.
065         */
066        public void deleteEntriesOlderThan(Date theCutoffDate) {
067                ourLog.debug("About to delete SearchUrl which are older than {}", theCutoffDate);
068                int deletedCount = myResourceSearchUrlDao.deleteAllWhereCreatedBefore(theCutoffDate);
069                ourLog.debug("Deleted {} SearchUrls", deletedCount);
070        }
071
072        /**
073         * Once a resource is updated or deleted, we can trust that future match checks will find the committed resource in the db.
074         * The use of the constraint table is done, and we can delete it to keep the table small.
075         */
076        public void deleteByResId(long theResId) {
077                myResourceSearchUrlDao.deleteByResId(theResId);
078        }
079
080        /**
081         *  We store a record of match urls with res_id so a db constraint can catch simultaneous creates that slip through.
082         */
083        public void enforceMatchUrlResourceUniqueness(
084                        String theResourceName, String theMatchUrl, JpaPid theResourcePersistentId) {
085                String canonicalizedUrlForStorage = createCanonicalizedUrlForStorage(theResourceName, theMatchUrl);
086
087                ResourceSearchUrlEntity searchUrlEntity =
088                                ResourceSearchUrlEntity.from(canonicalizedUrlForStorage, theResourcePersistentId.getId());
089                // calling dao.save performs a merge operation which implies a trip to
090                // the database to see if the resource exists.  Since we don't need the check, we avoid the trip by calling
091                // em.persist.
092                myEntityManager.persist(searchUrlEntity);
093        }
094
095        /**
096         * Provides a sanitized matchUrl to circumvent ordering matters.
097         */
098        private String createCanonicalizedUrlForStorage(String theResourceName, String theMatchUrl) {
099
100                RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceName);
101                SearchParameterMap matchUrlSearchParameterMap = myMatchUrlService.translateMatchUrl(theMatchUrl, resourceDef);
102
103                String canonicalizedMatchUrl = matchUrlSearchParameterMap.toNormalizedQueryString(myFhirContext);
104
105                return theResourceName + canonicalizedMatchUrl;
106        }
107}