
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