001/*-
002 * #%L
003 * HAPI FHIR Subscription 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.topic;
021
022import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
023import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
024import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
025import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
026import ca.uhn.fhir.rest.server.messaging.BaseResourceMessage;
027import ca.uhn.fhir.storage.PreviousVersionReader;
028import ca.uhn.fhir.util.Logs;
029import org.hl7.fhir.instance.model.api.IBaseResource;
030import org.hl7.fhir.r5.model.Enumeration;
031import org.hl7.fhir.r5.model.SubscriptionTopic;
032import org.slf4j.Logger;
033
034import java.util.List;
035import java.util.Optional;
036
037public class SubscriptionTriggerMatcher {
038        private static final Logger ourLog = Logs.getSubscriptionTopicLog();
039
040        private final SubscriptionTopicSupport mySubscriptionTopicSupport;
041        private final BaseResourceMessage.OperationTypeEnum myOperation;
042        private final SubscriptionTopic.SubscriptionTopicResourceTriggerComponent myTrigger;
043        private final String myResourceName;
044        private final IBaseResource myResource;
045        private final IFhirResourceDao myDao;
046        private final PreviousVersionReader myPreviousVersionReader;
047        private final SystemRequestDetails mySrd;
048
049        public SubscriptionTriggerMatcher(
050                        SubscriptionTopicSupport theSubscriptionTopicSupport,
051                        ResourceModifiedMessage theMsg,
052                        SubscriptionTopic.SubscriptionTopicResourceTriggerComponent theTrigger) {
053                mySubscriptionTopicSupport = theSubscriptionTopicSupport;
054                myOperation = theMsg.getOperationType();
055                myResource = theMsg.getPayload(theSubscriptionTopicSupport.getFhirContext());
056                myResourceName = myResource.fhirType();
057                myDao = mySubscriptionTopicSupport.getDaoRegistry().getResourceDao(myResourceName);
058                myTrigger = theTrigger;
059                myPreviousVersionReader = new PreviousVersionReader(myDao);
060                mySrd = new SystemRequestDetails();
061        }
062
063        public InMemoryMatchResult match() {
064                List<Enumeration<SubscriptionTopic.InteractionTrigger>> supportedInteractions =
065                                myTrigger.getSupportedInteraction();
066                if (SubscriptionTopicUtil.matches(myOperation, supportedInteractions)) {
067                        SubscriptionTopic.SubscriptionTopicResourceTriggerQueryCriteriaComponent queryCriteria =
068                                        myTrigger.getQueryCriteria();
069                        InMemoryMatchResult result = match(queryCriteria);
070                        if (result.matched()) {
071                                return result;
072                        }
073                }
074                return InMemoryMatchResult.noMatch();
075        }
076
077        private InMemoryMatchResult match(
078                        SubscriptionTopic.SubscriptionTopicResourceTriggerQueryCriteriaComponent theQueryCriteria) {
079                String previousCriteria = theQueryCriteria.getPrevious();
080                String currentCriteria = theQueryCriteria.getCurrent();
081                InMemoryMatchResult previousMatches = InMemoryMatchResult.fromBoolean(previousCriteria == null);
082                InMemoryMatchResult currentMatches = InMemoryMatchResult.fromBoolean(currentCriteria == null);
083
084                // WIP STR5 implement fhirPathCriteria per https://build.fhir.org/subscriptiontopic.html#fhirpath-criteria
085                if (currentCriteria != null) {
086                        currentMatches = matchResource(myResource, currentCriteria);
087                }
088                if (myOperation == ResourceModifiedMessage.OperationTypeEnum.CREATE) {
089                        return currentMatches;
090                }
091
092                if (previousCriteria != null) {
093                        if (myOperation == ResourceModifiedMessage.OperationTypeEnum.UPDATE
094                                        || myOperation == ResourceModifiedMessage.OperationTypeEnum.DELETE) {
095
096                                Optional<IBaseResource> oPreviousVersion = myPreviousVersionReader.readPreviousVersion(myResource);
097                                if (oPreviousVersion.isPresent()) {
098                                        previousMatches = matchResource(oPreviousVersion.get(), previousCriteria);
099                                } else {
100                                        ourLog.warn(
101                                                        "Resource {} has a version of 1, which should not be the case for a create or delete operation",
102                                                        myResource.getIdElement().toUnqualifiedVersionless());
103                                }
104                        }
105                }
106                // WIP STR5 implement resultForCreate and resultForDelete
107                if (theQueryCriteria.getRequireBoth()) {
108                        return InMemoryMatchResult.and(previousMatches, currentMatches);
109                } else {
110                        return InMemoryMatchResult.or(previousMatches, currentMatches);
111                }
112        }
113
114        private InMemoryMatchResult matchResource(IBaseResource theResource, String theCriteria) {
115                InMemoryMatchResult result =
116                                mySubscriptionTopicSupport.getSearchParamMatcher().match(theCriteria, theResource, mySrd);
117                if (!result.supported()) {
118                        ourLog.warn(
119                                        "Subscription topic {} has a query criteria that is not supported in-memory: {}",
120                                        myTrigger.getId(),
121                                        theCriteria);
122                }
123                return result;
124        }
125}