001package ca.uhn.fhir.jpa.subscription.channel.subscription;
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.subscription.channel.api.ChannelConsumerSettings;
024import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings;
025import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer;
026import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver;
027import ca.uhn.fhir.jpa.subscription.channel.models.ProducingChannelParameters;
028import ca.uhn.fhir.jpa.subscription.channel.models.ReceivingChannelParameters;
029import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
030import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
031import ca.uhn.fhir.jpa.subscription.model.ChannelRetryConfiguration;
032import com.google.common.collect.Multimap;
033import com.google.common.collect.MultimapBuilder;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036import org.springframework.beans.factory.annotation.Autowired;
037import org.springframework.messaging.MessageChannel;
038import org.springframework.messaging.MessageHandler;
039
040import java.util.Map;
041import java.util.Optional;
042import java.util.concurrent.ConcurrentHashMap;
043
044public class SubscriptionChannelRegistry {
045        private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionRegistry.class);
046
047        private final SubscriptionChannelCache myDeliveryReceiverChannels = new SubscriptionChannelCache();
048        // This map is a reference count so we know to destroy the channel when there are no more active subscriptions using it
049        // Key Channel Name, Value Subscription Id
050        private final Multimap<String, String> myActiveSubscriptionByChannelName = MultimapBuilder.hashKeys().arrayListValues().build();
051        private final Map<String, IChannelProducer> myChannelNameToSender = new ConcurrentHashMap<>();
052
053        @Autowired
054        private SubscriptionDeliveryHandlerFactory mySubscriptionDeliveryHandlerFactory;
055        @Autowired
056        private SubscriptionChannelFactory mySubscriptionDeliveryChannelFactory;
057
058        public synchronized void add(ActiveSubscription theActiveSubscription) {
059                String channelName = theActiveSubscription.getChannelName();
060                ourLog.info("Adding subscription {} to channel {}", theActiveSubscription.getId(), channelName);
061                myActiveSubscriptionByChannelName.put(channelName, theActiveSubscription.getId());
062
063                if (myDeliveryReceiverChannels.containsKey(channelName)) {
064                        ourLog.info("Channel {} already exists.  Not creating.", channelName);
065                        return;
066                }
067
068                // we get the retry configurations from the cannonicalized subscriber
069                // these will be provided to both the producer and receiver channel
070                ChannelRetryConfiguration retryConfigParameters = theActiveSubscription.getRetryConfigurationParameters();
071
072                /*
073                 * When we create a subscription, we create both
074                 * a producing/sending channel and
075                 * a receiving channel.
076                 *
077                 * Matched subscriptions are sent to the Sending channel
078                 * and the sending channel sends to subscription matching service.
079                 *
080                 * Receiving channel will send it out to
081                 * the subscriber hook (REST, email, etc).
082                 */
083
084                // the receiving channel
085                // this sends to the hook (resthook/message/email/whatever)
086                ReceivingChannelParameters receivingParameters = new ReceivingChannelParameters(channelName);
087                receivingParameters.setRetryConfiguration(retryConfigParameters);
088
089                IChannelReceiver channelReceiver = newReceivingChannel(receivingParameters);
090                Optional<MessageHandler> deliveryHandler = mySubscriptionDeliveryHandlerFactory.createDeliveryHandler(theActiveSubscription.getChannelType());
091
092                SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = new SubscriptionChannelWithHandlers(channelName, channelReceiver);
093                deliveryHandler.ifPresent(subscriptionChannelWithHandlers::addHandler);
094                myDeliveryReceiverChannels.put(channelName, subscriptionChannelWithHandlers);
095
096                // create the producing channel.
097                // channel used for sending to subscription matcher
098                ProducingChannelParameters producingChannelParameters = new ProducingChannelParameters(channelName);
099                producingChannelParameters.setRetryConfiguration(retryConfigParameters);
100
101                IChannelProducer sendingChannel = newSendingChannel(producingChannelParameters);
102                myChannelNameToSender.put(channelName, sendingChannel);
103        }
104
105        protected IChannelReceiver newReceivingChannel(ReceivingChannelParameters theParameters) {
106                ChannelConsumerSettings settings = new ChannelConsumerSettings();
107                settings.setRetryConfiguration(theParameters.getRetryConfiguration());
108                return mySubscriptionDeliveryChannelFactory.newDeliveryReceivingChannel(theParameters.getChannelName(),
109                        settings);
110        }
111
112        protected IChannelProducer newSendingChannel(ProducingChannelParameters theParameters) {
113                ChannelProducerSettings settings = new ChannelProducerSettings();
114                settings.setRetryConfiguration(theParameters.getRetryConfiguration());
115                return mySubscriptionDeliveryChannelFactory.newDeliverySendingChannel(theParameters.getChannelName(),
116                        settings);
117        }
118
119        public synchronized void remove(ActiveSubscription theActiveSubscription) {
120                String channelName = theActiveSubscription.getChannelName();
121                ourLog.info("Removing subscription {} from channel {}", theActiveSubscription.getId(), channelName);
122                boolean removed = myActiveSubscriptionByChannelName.remove(channelName, theActiveSubscription.getId());
123                ChannelRetryConfiguration retryConfig = theActiveSubscription.getRetryConfigurationParameters();
124
125                if (!removed) {
126                        ourLog.warn("Failed to remove subscription {} from channel {}", theActiveSubscription.getId(), channelName);
127                }
128
129                // This was the last one.  Close and remove the channel
130                if (!myActiveSubscriptionByChannelName.containsKey(channelName)) {
131                        SubscriptionChannelWithHandlers channel = myDeliveryReceiverChannels.get(channelName);
132                        if (channel != null) {
133                                channel.close();
134                        }
135                        myDeliveryReceiverChannels.closeAndRemove(channelName);
136                        myChannelNameToSender.remove(channelName);
137                }
138
139        }
140
141        public synchronized SubscriptionChannelWithHandlers getDeliveryReceiverChannel(String theChannelName) {
142                return myDeliveryReceiverChannels.get(theChannelName);
143        }
144
145        public synchronized MessageChannel getDeliverySenderChannel(String theChannelName) {
146                return myChannelNameToSender.get(theChannelName);
147        }
148
149        public synchronized int size() {
150                return myDeliveryReceiverChannels.size();
151        }
152}