001/*-
002 * #%L
003 * HAPI FHIR Storage api
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.storage.interceptor.balp;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.rest.client.api.IGenericClient;
024import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
025import jakarta.annotation.Nonnull;
026import jakarta.annotation.Nullable;
027import org.apache.commons.lang3.Validate;
028import org.hl7.fhir.instance.model.api.IBaseResource;
029import org.hl7.fhir.r4.model.AuditEvent;
030
031import java.util.List;
032
033public class FhirClientBalpSink implements IBalpAuditEventSink {
034
035        protected final IGenericClient myClient;
036        private final VersionCanonicalizer myVersionCanonicalizer;
037
038        /**
039         * Sets the FhirContext to use when initiating outgoing connections
040         *
041         * @param theFhirContext   The FhirContext instance. This context must be
042         *                         for the FHIR Version supported by the target/sink
043         *                         server (as opposed to the FHIR Version supported
044         *                         by the audit source).
045         * @param theTargetBaseUrl The FHIR server base URL for the target/sink server to
046         *                         receive audit events.
047         */
048        public FhirClientBalpSink(@Nonnull FhirContext theFhirContext, @Nonnull String theTargetBaseUrl) {
049                this(theFhirContext, theTargetBaseUrl, null);
050        }
051
052        /**
053         * Sets the FhirContext to use when initiating outgoing connections
054         *
055         * @param theFhirContext        The FhirContext instance. This context must be
056         *                              for the FHIR Version supported by the target/sink
057         *                              server (as opposed to the FHIR Version supported
058         *                              by the audit source).
059         * @param theTargetBaseUrl      The FHIR server base URL for the target/sink server to
060         *                              receive audit events.
061         * @param theClientInterceptors An optional list of interceptors to register against
062         *                              the client. May be {@literal null}.
063         */
064        public FhirClientBalpSink(
065                        @Nonnull FhirContext theFhirContext,
066                        @Nonnull String theTargetBaseUrl,
067                        @Nullable List<Object> theClientInterceptors) {
068                this(createClient(theFhirContext, theTargetBaseUrl, theClientInterceptors));
069        }
070
071        /**
072         * Constructor
073         *
074         * @param theClient The FHIR client to use as a sink.
075         */
076        public FhirClientBalpSink(IGenericClient theClient) {
077                myClient = theClient;
078                myVersionCanonicalizer = new VersionCanonicalizer(myClient.getFhirContext());
079        }
080
081        @Override
082        public void recordAuditEvent(AuditEvent theAuditEvent) {
083                IBaseResource auditEvent = myVersionCanonicalizer.auditEventFromCanonical(theAuditEvent);
084                recordAuditEvent(auditEvent);
085        }
086
087        protected void recordAuditEvent(IBaseResource auditEvent) {
088                transmitEventToClient(auditEvent);
089        }
090
091        protected void transmitEventToClient(IBaseResource auditEvent) {
092                myClient.create().resource(auditEvent).execute();
093        }
094
095        static IGenericClient createClient(
096                        @Nonnull FhirContext theFhirContext,
097                        @Nonnull String theTargetBaseUrl,
098                        @Nullable List<Object> theClientInterceptors) {
099                Validate.notNull(theFhirContext, "theFhirContext must not be null");
100                Validate.notBlank(theTargetBaseUrl, "theTargetBaseUrl must not be null or blank");
101                IGenericClient client = theFhirContext.newRestfulGenericClient(theTargetBaseUrl);
102                if (theClientInterceptors != null) {
103                        theClientInterceptors.forEach(client::registerInterceptor);
104                }
105                return client;
106        }
107}