001package ca.uhn.fhir.rest.api.server.storage;
002
003/*-
004 * #%L
005 * HAPI FHIR - Server Framework
006 * %%
007 * Copyright (C) 2014 - 2021 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.interceptor.api.HookParams;
024import ca.uhn.fhir.interceptor.api.Pointcut;
025import com.google.common.collect.ArrayListMultimap;
026import com.google.common.collect.ListMultimap;
027import org.apache.commons.lang3.Validate;
028import org.hl7.fhir.instance.model.api.IIdType;
029
030import javax.annotation.Nullable;
031import java.util.Collections;
032import java.util.Date;
033import java.util.EnumSet;
034import java.util.HashMap;
035import java.util.Map;
036import java.util.function.Supplier;
037
038/**
039 * This object contains runtime information that is gathered and relevant to a single <i>database transaction</i>.
040 * This doesn't mean a FHIR transaction necessarily, but rather any operation that happens within a single DB transaction
041 * (i.e. a FHIR create, read, transaction, etc.).
042 * <p>
043 * The intent with this class is to hold things we want to pass from operation to operation within a transaction in
044 * order to avoid looking things up multiple times, etc.
045 * </p>
046 *
047 * @since 5.0.0
048 */
049public class TransactionDetails {
050
051        private final Date myTransactionDate;
052        private Map<String, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
053        private Map<String, Object> myUserData;
054        private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
055        private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
056
057        /**
058         * Constructor
059         */
060        public TransactionDetails() {
061                this(new Date());
062        }
063
064        /**
065         * Constructor
066         */
067        public TransactionDetails(Date theTransactionDate) {
068                myTransactionDate = theTransactionDate;
069        }
070
071        /**
072         * A <b>Resolved Resource ID</b> is a mapping between a resource ID (e.g. "<code>Patient/ABC</code>" or
073         * "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
074         * the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
075         */
076        @Nullable
077        public ResourcePersistentId getResolvedResourceId(IIdType theId) {
078                String idValue = theId.toVersionless().getValue();
079                return myResolvedResourceIds.get(idValue);
080        }
081
082        /**
083         * A <b>Resolved Resource ID</b> is a mapping between a resource ID (e.g. "<code>Patient/ABC</code>" or
084         * "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
085         * the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
086         */
087        public void addResolvedResourceId(IIdType theResourceId, ResourcePersistentId thePersistentId) {
088                assert theResourceId != null;
089                assert thePersistentId != null;
090
091                if (myResolvedResourceIds.isEmpty()) {
092                        myResolvedResourceIds = new HashMap<>();
093                }
094                myResolvedResourceIds.put(theResourceId.toVersionless().getValue(), thePersistentId);
095        }
096
097        /**
098         * This is the wall-clock time that a given transaction started.
099         */
100        public Date getTransactionDate() {
101                return myTransactionDate;
102        }
103
104        /**
105         * Sets an arbitraty object that will last the lifetime of the current transaction
106         *
107         * @see #getUserData(String)
108         */
109        public void putUserData(String theKey, Object theValue) {
110                if (myUserData == null) {
111                        myUserData = new HashMap<>();
112                }
113                myUserData.put(theKey, theValue);
114        }
115
116        /**
117         * Gets an arbitrary object that will last the lifetime of the current transaction
118         *
119         * @see #putUserData(String, Object)
120         */
121        @SuppressWarnings("unchecked")
122        public <T> T getUserData(String theKey) {
123                if (myUserData != null) {
124                        return (T) myUserData.get(theKey);
125                }
126                return null;
127        }
128
129        /**
130         * Fetches the existing value in the user data map, or uses {@literal theSupplier} to create a new object and
131         * puts that in the map, and returns it
132         */
133        @SuppressWarnings("unchecked")
134        public <T> T getOrCreateUserData(String theKey, Supplier<T> theSupplier) {
135                T retVal = (T) getUserData(theKey);
136                if (retVal == null) {
137                        retVal = theSupplier.get();
138                        putUserData(theKey, retVal);
139                }
140                return retVal;
141        }
142
143        /**
144         * This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
145         *
146         * @since 5.2.0
147         */
148        public void beginAcceptingDeferredInterceptorBroadcasts(Pointcut... thePointcuts) {
149                Validate.isTrue(!isAcceptingDeferredInterceptorBroadcasts());
150                myDeferredInterceptorBroadcasts = ArrayListMultimap.create();
151                myDeferredInterceptorBroadcastPointcuts = EnumSet.of(thePointcuts[0], thePointcuts);
152        }
153
154        /**
155         * This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
156         *
157         * @since 5.2.0
158         */
159        public boolean isAcceptingDeferredInterceptorBroadcasts() {
160                return myDeferredInterceptorBroadcasts != null;
161        }
162
163        /**
164         * This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
165         *
166         * @since 5.2.0
167         */
168        public boolean isAcceptingDeferredInterceptorBroadcasts(Pointcut thePointcut) {
169                return myDeferredInterceptorBroadcasts != null && myDeferredInterceptorBroadcastPointcuts.contains(thePointcut);
170        }
171
172        /**
173         * This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
174         *
175         * @since 5.2.0
176         */
177        public ListMultimap<Pointcut, HookParams> endAcceptingDeferredInterceptorBroadcasts() {
178                Validate.isTrue(isAcceptingDeferredInterceptorBroadcasts());
179                ListMultimap<Pointcut, HookParams> retVal = myDeferredInterceptorBroadcasts;
180                myDeferredInterceptorBroadcasts = null;
181                myDeferredInterceptorBroadcastPointcuts = null;
182                return retVal;
183        }
184
185        /**
186         * This can be used by processors for FHIR transactions to defer interceptor broadcasts on sub-requests if needed
187         *
188         * @since 5.2.0
189         */
190        public void addDeferredInterceptorBroadcast(Pointcut thePointcut, HookParams theHookParams) {
191                Validate.isTrue(isAcceptingDeferredInterceptorBroadcasts(thePointcut));
192                myDeferredInterceptorBroadcasts.put(thePointcut, theHookParams);
193        }
194}
195