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}