001package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber; 002 003/*- 004 * #%L 005 * HAPI FHIR Subscription Server 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.jpa.api.config.DaoConfig; 024import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 025import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 026import ca.uhn.fhir.jpa.partition.SystemRequestDetails; 027import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; 028import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; 029import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants; 030import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; 031import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; 032import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; 033import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; 034import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; 035import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 036import ca.uhn.fhir.util.SubscriptionUtil; 037import org.hl7.fhir.instance.model.api.IBaseResource; 038import org.hl7.fhir.r4.model.Subscription; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041import org.springframework.beans.factory.annotation.Autowired; 042import org.springframework.messaging.Message; 043import org.springframework.messaging.MessageHandler; 044import org.springframework.messaging.MessagingException; 045 046import javax.annotation.Nonnull; 047 048/** 049 * Responsible for transitioning subscription resources from REQUESTED to ACTIVE 050 * Once activated, the subscription is added to the SubscriptionRegistry. 051 * <p> 052 * Also validates criteria. If invalid, rejects the subscription without persisting the subscription. 053 */ 054public class SubscriptionActivatingSubscriber extends BaseSubscriberForSubscriptionResources implements MessageHandler { 055 private final Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class); 056 @Autowired 057 private SubscriptionRegistry mySubscriptionRegistry; 058 @Autowired 059 private DaoRegistry myDaoRegistry; 060 @Autowired 061 private SubscriptionCanonicalizer mySubscriptionCanonicalizer; 062 @Autowired 063 private DaoConfig myDaoConfig; 064 @Autowired 065 private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; 066 067 /** 068 * Constructor 069 */ 070 public SubscriptionActivatingSubscriber() { 071 super(); 072 } 073 074 @Override 075 public void handleMessage(@Nonnull Message<?> theMessage) throws MessagingException { 076 if (!(theMessage instanceof ResourceModifiedJsonMessage)) { 077 ourLog.warn("Received message of unexpected type on matching channel: {}", theMessage); 078 return; 079 } 080 081 ResourceModifiedMessage payload = ((ResourceModifiedJsonMessage) theMessage).getPayload(); 082 if (!isSubscription(payload)) { 083 return; 084 } 085 086 switch (payload.getOperationType()) { 087 case CREATE: 088 case UPDATE: 089 activateSubscriptionIfRequired(payload.getNewPayload(myFhirContext)); 090 break; 091 case DELETE: 092 case MANUALLY_TRIGGERED: 093 default: 094 break; 095 } 096 097 } 098 099 public boolean activateSubscriptionIfRequired(final IBaseResource theSubscription) { 100 // Grab the value for "Subscription.channel.type" so we can see if this 101 // subscriber applies.. 102 CanonicalSubscriptionChannelType subscriptionChannelType = mySubscriptionCanonicalizer.getChannelType(theSubscription); 103 104 // Only activate supported subscriptions 105 if (subscriptionChannelType == null 106 || !myDaoConfig.getSupportedSubscriptionTypes().contains(subscriptionChannelType.toCanonical())) { 107 return false; 108 } 109 110 String statusString = mySubscriptionCanonicalizer.getSubscriptionStatus(theSubscription); 111 112 if (SubscriptionConstants.REQUESTED_STATUS.equals(statusString)) { 113 return activateSubscription(theSubscription); 114 } 115 116 return false; 117 } 118 119 @SuppressWarnings("unchecked") 120 private boolean activateSubscription(final IBaseResource theSubscription) { 121 IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); 122 SystemRequestDetails srd = SystemRequestDetails.forAllPartition(); 123 124 IBaseResource subscription = null; 125 try { 126 // read can throw ResourceGoneException 127 // if this happens, we will treat this as a failure to activate 128 subscription = subscriptionDao.read(theSubscription.getIdElement(), SystemRequestDetails.forAllPartition()); 129 subscription.setId(subscription.getIdElement().toVersionless()); 130 131 ourLog.info("Activating subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), SubscriptionConstants.REQUESTED_STATUS, SubscriptionConstants.ACTIVE_STATUS); 132 SubscriptionUtil.setStatus(myFhirContext, subscription, SubscriptionConstants.ACTIVE_STATUS); 133 subscriptionDao.update(subscription, srd); 134 return true; 135 } catch (final UnprocessableEntityException | ResourceGoneException e) { 136 subscription = subscription != null ? subscription : theSubscription; 137 ourLog.error("Failed to activate subscription " 138 + subscription.getIdElement() 139 + " : " + e.getMessage()); 140 ourLog.info("Changing status of {} to ERROR", subscription.getIdElement()); 141 SubscriptionUtil.setStatus(myFhirContext, subscription, SubscriptionConstants.ERROR_STATUS); 142 SubscriptionUtil.setReason(myFhirContext, subscription, e.getMessage()); 143 subscriptionDao.update(subscription, srd); 144 return false; 145 } 146 } 147 148}