
001/*- 002 * #%L 003 * HAPI FHIR Subscription Server 004 * %% 005 * Copyright (C) 2014 - 2025 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.subscription.match.registry; 021 022import ca.uhn.fhir.cache.BaseResourceCacheSynchronizer; 023import ca.uhn.fhir.interceptor.model.RequestPartitionId; 024import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 025import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionActivatingListener; 026import ca.uhn.fhir.rest.param.TokenOrListParam; 027import ca.uhn.fhir.rest.param.TokenParam; 028import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 029import ca.uhn.fhir.subscription.SubscriptionConstants; 030import com.google.common.annotations.VisibleForTesting; 031import jakarta.annotation.Nonnull; 032import org.apache.commons.lang3.StringUtils; 033import org.hl7.fhir.instance.model.api.IBaseResource; 034import org.hl7.fhir.r4.model.Subscription; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037import org.springframework.beans.factory.annotation.Autowired; 038 039import java.util.HashSet; 040import java.util.List; 041import java.util.Set; 042 043public class SubscriptionLoader extends BaseResourceCacheSynchronizer { 044 private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionLoader.class); 045 046 @Autowired 047 private SubscriptionRegistry mySubscriptionRegistry; 048 049 @Autowired 050 private SubscriptionActivatingListener mySubscriptionActivatingInterceptor; 051 052 @Autowired 053 private SubscriptionCanonicalizer mySubscriptionCanonicalizer; 054 055 @Autowired 056 protected ISearchParamRegistry mySearchParamRegistry; 057 058 /** 059 * Constructor 060 */ 061 public SubscriptionLoader() { 062 super("Subscription", RequestPartitionId.allPartitions()); 063 } 064 065 @VisibleForTesting 066 public int doSyncSubscriptionsForUnitTest() { 067 return super.doSyncResourcesForUnitTest(); 068 } 069 070 @Override 071 @Nonnull 072 protected SearchParameterMap getSearchParameterMap() { 073 SearchParameterMap map = new SearchParameterMap(); 074 075 if (mySearchParamRegistry.getActiveSearchParam( 076 "Subscription", "status", ISearchParamRegistry.SearchParamLookupContextEnum.ALL) 077 != null) { 078 map.add( 079 Subscription.SP_STATUS, 080 new TokenOrListParam() 081 .addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode())) 082 .addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()))); 083 } 084 map.setLoadSynchronousUpTo(SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS); 085 return map; 086 } 087 088 @Override 089 protected void handleInit(List<IBaseResource> resourceList) { 090 updateSubscriptionRegistry(resourceList); 091 } 092 093 @Override 094 protected int syncResourcesIntoCache(List<IBaseResource> resourceList) { 095 return updateSubscriptionRegistry(resourceList); 096 } 097 098 private int updateSubscriptionRegistry(List<IBaseResource> theResourceList) { 099 Set<String> allIds = new HashSet<>(); 100 int activatedCount = 0; 101 int registeredCount = 0; 102 103 for (IBaseResource resource : theResourceList) { 104 String nextId = resource.getIdElement().getIdPart(); 105 allIds.add(nextId); 106 107 boolean activated = activateSubscriptionIfRequested(resource); 108 if (activated) { 109 ++activatedCount; 110 } 111 112 boolean registered = mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(resource); 113 if (registered) { 114 registeredCount++; 115 } 116 } 117 118 mySubscriptionRegistry.unregisterAllSubscriptionsNotInCollection(allIds); 119 ourLog.debug( 120 "Finished sync subscriptions - activated {} and registered {}", 121 theResourceList.size(), 122 registeredCount); 123 return activatedCount; 124 } 125 126 /** 127 * Check status of theSubscription and update to "active" if needed. 128 * @return true if activated 129 */ 130 private boolean activateSubscriptionIfRequested(IBaseResource theSubscription) { 131 boolean successfullyActivated = false; 132 133 if (SubscriptionConstants.REQUESTED_STATUS.equals( 134 mySubscriptionCanonicalizer.getSubscriptionStatus(theSubscription))) { 135 if (mySubscriptionActivatingInterceptor.isChannelTypeSupported(theSubscription)) { 136 // internally, subscriptions that cannot activate will be set to error 137 if (mySubscriptionActivatingInterceptor.activateSubscriptionIfRequired(theSubscription)) { 138 successfullyActivated = true; 139 } else { 140 logSubscriptionNotActivatedPlusErrorIfPossible(theSubscription); 141 } 142 } else { 143 ourLog.debug( 144 "Could not activate subscription {} because channel type {} is not supported.", 145 theSubscription.getIdElement(), 146 mySubscriptionCanonicalizer.getChannelType(theSubscription)); 147 } 148 } 149 150 return successfullyActivated; 151 } 152 153 /** 154 * Logs 155 * 156 * @param theSubscription 157 */ 158 private void logSubscriptionNotActivatedPlusErrorIfPossible(IBaseResource theSubscription) { 159 String error; 160 if (theSubscription instanceof Subscription) { 161 error = ((Subscription) theSubscription).getError(); 162 } else if (theSubscription instanceof org.hl7.fhir.dstu3.model.Subscription) { 163 error = ((org.hl7.fhir.dstu3.model.Subscription) theSubscription).getError(); 164 } else if (theSubscription instanceof org.hl7.fhir.dstu2.model.Subscription) { 165 error = ((org.hl7.fhir.dstu2.model.Subscription) theSubscription).getError(); 166 } else { 167 error = ""; 168 } 169 ourLog.error( 170 "Subscription {} could not be activated. " 171 + "This will not prevent startup, but it could lead to undesirable outcomes! {}", 172 theSubscription.getIdElement().getIdPart(), 173 (StringUtils.isBlank(error) ? "" : "Error: " + error)); 174 } 175}