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}