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.dao;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.interceptor.model.RequestPartitionId;
024import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
025import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
026import ca.uhn.fhir.jpa.model.dao.JpaPid;
027import ca.uhn.fhir.jpa.model.entity.ResourceTable;
028import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
029import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
030import ca.uhn.fhir.model.api.IQueryParameterType;
031import ca.uhn.fhir.rest.api.CacheControlDirective;
032import ca.uhn.fhir.rest.api.Constants;
033import ca.uhn.fhir.rest.api.SortOrderEnum;
034import ca.uhn.fhir.rest.api.SortSpec;
035import ca.uhn.fhir.rest.api.server.IBundleProvider;
036import ca.uhn.fhir.rest.api.server.RequestDetails;
037import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
038import ca.uhn.fhir.rest.param.ReferenceOrListParam;
039import ca.uhn.fhir.rest.param.ReferenceParam;
040import org.hl7.fhir.instance.model.api.IBaseResource;
041import org.hl7.fhir.r4.model.Observation;
042import org.springframework.beans.factory.annotation.Autowired;
043import org.springframework.transaction.support.TransactionTemplate;
044
045import java.util.ArrayList;
046import java.util.Date;
047import java.util.List;
048import java.util.TreeMap;
049import javax.persistence.EntityManager;
050import javax.persistence.PersistenceContext;
051import javax.persistence.PersistenceContextType;
052import javax.servlet.http.HttpServletResponse;
053
054public class JpaResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T>
055                implements IFhirResourceDaoObservation<T> {
056
057        @PersistenceContext(type = PersistenceContextType.TRANSACTION)
058        protected EntityManager myEntityManager;
059
060        @Autowired
061        ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
062
063        @Autowired
064        private IRequestPartitionHelperSvc myRequestPartitionHelperService;
065
066        @Override
067        public IBundleProvider observationsLastN(
068                        SearchParameterMap theSearchParameterMap,
069                        RequestDetails theRequestDetails,
070                        HttpServletResponse theServletResponse) {
071                updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
072
073                RequestPartitionId requestPartitionId =
074                                myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(
075                                                theRequestDetails, getResourceName(), theSearchParameterMap, null);
076                return mySearchCoordinatorSvc.registerSearch(
077                                this,
078                                theSearchParameterMap,
079                                getResourceName(),
080                                new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)),
081                                theRequestDetails,
082                                requestPartitionId);
083        }
084
085        private String getEffectiveParamName() {
086                return Observation.SP_DATE;
087        }
088
089        private String getCodeParamName() {
090                return Observation.SP_CODE;
091        }
092
093        private String getSubjectParamName() {
094                return Observation.SP_SUBJECT;
095        }
096
097        private String getPatientParamName() {
098                return Observation.SP_PATIENT;
099        }
100
101        @Override
102        public ResourceTable updateEntity(
103                        RequestDetails theRequest,
104                        IBaseResource theResource,
105                        IBasePersistedResource theEntity,
106                        Date theDeletedTimestampOrNull,
107                        boolean thePerformIndexing,
108                        boolean theUpdateVersion,
109                        TransactionDetails theTransactionDetails,
110                        boolean theForceUpdate,
111                        boolean theCreateNewHistoryEntry) {
112                return updateObservationEntity(
113                                theRequest,
114                                theResource,
115                                theEntity,
116                                theDeletedTimestampOrNull,
117                                thePerformIndexing,
118                                theUpdateVersion,
119                                theTransactionDetails,
120                                theForceUpdate,
121                                theCreateNewHistoryEntry);
122        }
123
124        protected ResourceTable updateObservationEntity(
125                        RequestDetails theRequest,
126                        IBaseResource theResource,
127                        IBasePersistedResource theEntity,
128                        Date theDeletedTimestampOrNull,
129                        boolean thePerformIndexing,
130                        boolean theUpdateVersion,
131                        TransactionDetails theTransactionDetails,
132                        boolean theForceUpdate,
133                        boolean theCreateNewHistoryEntry) {
134                ResourceTable retVal = super.updateEntity(
135                                theRequest,
136                                theResource,
137                                theEntity,
138                                theDeletedTimestampOrNull,
139                                thePerformIndexing,
140                                theUpdateVersion,
141                                theTransactionDetails,
142                                theForceUpdate,
143                                theCreateNewHistoryEntry);
144
145                if (getStorageSettings().isLastNEnabled()) {
146                        if (!retVal.isUnchangedInCurrentOperation()) {
147                                if (retVal.getDeleted() == null) {
148                                        // Update indexes here for LastN operation.
149                                        myObservationLastNIndexPersistSvc.indexObservation(theResource);
150                                } else {
151                                        myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
152                                }
153                        }
154                }
155
156                return retVal;
157        }
158
159        protected void updateSearchParamsForLastn(
160                        SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
161                if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
162                        theSearchParameterMap.setLoadSynchronous(true);
163                }
164
165                theSearchParameterMap.setLastN(true);
166                SortSpec effectiveDtm = new SortSpec(getEffectiveParamName()).setOrder(SortOrderEnum.DESC);
167                SortSpec observationCode =
168                                new SortSpec(getCodeParamName()).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
169                if (theSearchParameterMap.containsKey(getSubjectParamName())
170                                || theSearchParameterMap.containsKey(getPatientParamName())) {
171
172                        new TransactionTemplate(myPlatformTransactionManager)
173                                        .executeWithoutResult(
174                                                        tx -> fixSubjectParamsOrderForLastn(theSearchParameterMap, theRequestDetails));
175
176                        theSearchParameterMap.setSort(new SortSpec(getSubjectParamName())
177                                        .setOrder(SortOrderEnum.ASC)
178                                        .setChain(observationCode));
179                } else {
180                        theSearchParameterMap.setSort(observationCode);
181                }
182        }
183
184        private void fixSubjectParamsOrderForLastn(
185                        SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
186                // Need to ensure that the patient/subject parameters are sorted in the SearchParameterMap to ensure correct
187                // ordering of
188                // the output. The reason for this is that observations are indexed by patient/subject forced ID, but then
189                // ordered in the
190                // final result set by subject/patient resource PID.
191                TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>();
192                if (theSearchParameterMap.containsKey(getSubjectParamName())) {
193
194                        RequestPartitionId requestPartitionId =
195                                        myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(
196                                                        theRequestDetails, getResourceName(), theSearchParameterMap, null);
197
198                        List<List<IQueryParameterType>> patientParams = new ArrayList<>();
199                        if (theSearchParameterMap.get(getPatientParamName()) != null) {
200                                patientParams.addAll(theSearchParameterMap.get(getPatientParamName()));
201                        }
202                        if (theSearchParameterMap.get(getSubjectParamName()) != null) {
203                                patientParams.addAll(theSearchParameterMap.get(getSubjectParamName()));
204                        }
205
206                        for (List<? extends IQueryParameterType> nextPatientList : patientParams) {
207                                for (IQueryParameterType nextOr : nextPatientList) {
208                                        if (nextOr instanceof ReferenceParam) {
209                                                ReferenceParam ref = (ReferenceParam) nextOr;
210                                                JpaPid pid = myIdHelperService.resolveResourcePersistentIds(
211                                                                requestPartitionId, ref.getResourceType(), ref.getIdPart());
212                                                orderedSubjectReferenceMap.put(pid.getId(), nextOr);
213                                        } else {
214                                                throw new IllegalArgumentException(
215                                                                Msg.code(942) + "Invalid token type (expecting ReferenceParam): " + nextOr.getClass());
216                                        }
217                                }
218                        }
219
220                        theSearchParameterMap.remove(getSubjectParamName());
221                        theSearchParameterMap.remove(getPatientParamName());
222
223                        // Subject PIDs ordered - so create 'OR' list of subjects for lastN operation
224                        ReferenceOrListParam orList = new ReferenceOrListParam();
225                        orderedSubjectReferenceMap
226                                        .keySet()
227                                        .forEach(key -> orList.addOr((ReferenceParam) orderedSubjectReferenceMap.get(key)));
228                        theSearchParameterMap.add(getSubjectParamName(), orList);
229                }
230        }
231}