001/*-
002 * #%L
003 * HAPI FHIR Subscription Server
004 * %%
005 * Copyright (C) 2014 - 2024 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.topic;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.FhirVersionEnum;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
026import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
027import ca.uhn.fhir.jpa.topic.status.INotificationStatusBuilder;
028import ca.uhn.fhir.jpa.topic.status.R4BNotificationStatusBuilder;
029import ca.uhn.fhir.jpa.topic.status.R4NotificationStatusBuilder;
030import ca.uhn.fhir.jpa.topic.status.R5NotificationStatusBuilder;
031import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
032import ca.uhn.fhir.util.BundleBuilder;
033import org.apache.commons.lang3.ObjectUtils;
034import org.hl7.fhir.instance.model.api.IBaseBundle;
035import org.hl7.fhir.instance.model.api.IBaseResource;
036import org.hl7.fhir.r5.model.Bundle;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040import java.util.List;
041
042import static org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE;
043
044public class SubscriptionTopicPayloadBuilder {
045        private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTopicPayloadBuilder.class);
046        private final FhirContext myFhirContext;
047        private final FhirVersionEnum myFhirVersion;
048        private final INotificationStatusBuilder<? extends IBaseResource> myNotificationStatusBuilder;
049
050        public SubscriptionTopicPayloadBuilder(FhirContext theFhirContext) {
051                myFhirContext = theFhirContext;
052                myFhirVersion = myFhirContext.getVersion().getVersion();
053
054                switch (myFhirVersion) {
055                        case R4:
056                                myNotificationStatusBuilder = new R4NotificationStatusBuilder(myFhirContext);
057                                break;
058                        case R4B:
059                                myNotificationStatusBuilder = new R4BNotificationStatusBuilder(myFhirContext);
060                                break;
061                        case R5:
062                                myNotificationStatusBuilder = new R5NotificationStatusBuilder(myFhirContext);
063                                break;
064                        default:
065                                throw unsupportedFhirVersionException();
066                }
067        }
068
069        public IBaseBundle buildPayload(
070                        List<IBaseResource> theResources,
071                        ActiveSubscription theActiveSubscription,
072                        String theTopicUrl,
073                        RestOperationTypeEnum theRestOperationType) {
074                BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext);
075
076                IBaseResource notificationStatus =
077                                myNotificationStatusBuilder.buildNotificationStatus(theResources, theActiveSubscription, theTopicUrl);
078                bundleBuilder.addCollectionEntry(notificationStatus);
079
080                addResources(theResources, theActiveSubscription.getSubscription(), theRestOperationType, bundleBuilder);
081                // WIP STR5 add support for notificationShape include, revinclude
082
083                // Note we need to set the bundle type after we add the resources since adding the resources automatically sets
084                // the bundle type
085                setBundleType(bundleBuilder);
086                IBaseBundle retval = bundleBuilder.getBundle();
087                if (ourLog.isDebugEnabled()) {
088                        String bundle = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(retval);
089                        ourLog.debug("Bundle: {}", bundle);
090                }
091                return retval;
092        }
093
094        private void addResources(
095                        List<IBaseResource> theResources,
096                        CanonicalSubscription theCanonicalSubscription,
097                        RestOperationTypeEnum theRestOperationType,
098                        BundleBuilder theBundleBuilder) {
099
100                org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent content =
101                                ObjectUtils.defaultIfNull(theCanonicalSubscription.getContent(), FULLRESOURCE);
102
103                switch (content) {
104                        case EMPTY:
105                                // skip adding resource to the Bundle
106                                break;
107                        case IDONLY:
108                                addIdOnly(theBundleBuilder, theResources, theRestOperationType);
109                                break;
110                        case FULLRESOURCE:
111                                addFullResources(theBundleBuilder, theResources, theRestOperationType);
112                                break;
113                }
114        }
115
116        private void addIdOnly(
117                        BundleBuilder bundleBuilder, List<IBaseResource> theResources, RestOperationTypeEnum theRestOperationType) {
118                for (IBaseResource resource : theResources) {
119                        switch (theRestOperationType) {
120                                case CREATE:
121                                        bundleBuilder.addTransactionCreateEntryIdOnly(resource);
122                                        break;
123                                case UPDATE:
124                                        bundleBuilder.addTransactionUpdateIdOnlyEntry(resource);
125                                        break;
126                                case DELETE:
127                                        bundleBuilder.addTransactionDeleteEntry(resource);
128                                        break;
129                        }
130                }
131        }
132
133        private void addFullResources(
134                        BundleBuilder bundleBuilder, List<IBaseResource> theResources, RestOperationTypeEnum theRestOperationType) {
135                for (IBaseResource resource : theResources) {
136                        switch (theRestOperationType) {
137                                case CREATE:
138                                        bundleBuilder.addTransactionCreateEntry(resource);
139                                        break;
140                                case UPDATE:
141                                        bundleBuilder.addTransactionUpdateEntry(resource);
142                                        break;
143                                case DELETE:
144                                        bundleBuilder.addTransactionDeleteEntry(resource);
145                                        break;
146                        }
147                }
148        }
149
150        private void setBundleType(BundleBuilder bundleBuilder) {
151                switch (myFhirVersion) {
152                        case R4:
153                        case R4B:
154                                bundleBuilder.setType(Bundle.BundleType.HISTORY.toCode());
155                                break;
156                        case R5:
157                                bundleBuilder.setType(Bundle.BundleType.SUBSCRIPTIONNOTIFICATION.toCode());
158                                break;
159                        default:
160                                throw unsupportedFhirVersionException();
161                }
162        }
163
164        private IllegalStateException unsupportedFhirVersionException() {
165                return new IllegalStateException(
166                                Msg.code(2331) + "SubscriptionTopic subscriptions are not supported on FHIR version: " + myFhirVersion);
167        }
168}