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}