
001/* 002 * #%L 003 * HAPI FHIR JPA 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.provider; 021 022import ca.uhn.fhir.batch2.jobs.merge.MergeResourceHelper; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 025import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; 026import ca.uhn.fhir.interceptor.model.RequestPartitionId; 027import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; 028import ca.uhn.fhir.jpa.interceptor.ProvenanceAgentsPointcutUtil; 029import ca.uhn.fhir.jpa.model.util.JpaConstants; 030import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; 031import ca.uhn.fhir.model.api.IProvenanceAgent; 032import ca.uhn.fhir.model.api.annotation.Description; 033import ca.uhn.fhir.model.primitive.IdDt; 034import ca.uhn.fhir.replacereferences.ReplaceReferencesRequest; 035import ca.uhn.fhir.replacereferences.UndoReplaceReferencesRequest; 036import ca.uhn.fhir.rest.annotation.Operation; 037import ca.uhn.fhir.rest.annotation.OperationParam; 038import ca.uhn.fhir.rest.annotation.Transaction; 039import ca.uhn.fhir.rest.annotation.TransactionParam; 040import ca.uhn.fhir.rest.api.server.RequestDetails; 041import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 042import ca.uhn.fhir.rest.server.provider.ProviderConstants; 043import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 044import ca.uhn.fhir.util.ParametersUtil; 045import jakarta.servlet.http.HttpServletResponse; 046import org.hl7.fhir.instance.model.api.IBaseBundle; 047import org.hl7.fhir.instance.model.api.IBaseParameters; 048import org.hl7.fhir.instance.model.api.IBaseResource; 049import org.hl7.fhir.instance.model.api.IPrimitiveType; 050import org.springframework.beans.factory.annotation.Autowired; 051 052import java.util.Collections; 053import java.util.List; 054import java.util.Map; 055import java.util.TreeMap; 056 057import static ca.uhn.fhir.rest.server.provider.ProviderConstants.OPERATION_REPLACE_REFERENCES_OUTPUT_PARAM_TASK; 058import static ca.uhn.fhir.rest.server.provider.ProviderConstants.OPERATION_REPLACE_REFERENCES_PARAM_SOURCE_REFERENCE_ID; 059import static ca.uhn.fhir.rest.server.provider.ProviderConstants.OPERATION_REPLACE_REFERENCES_PARAM_TARGET_REFERENCE_ID; 060import static ca.uhn.fhir.rest.server.provider.ProviderConstants.OPERATION_UNDO_REPLACE_REFERENCES; 061import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; 062import static org.apache.commons.lang3.StringUtils.isNotBlank; 063import static software.amazon.awssdk.utils.StringUtils.isBlank; 064 065public final class JpaSystemProvider<T, MT> extends BaseJpaSystemProvider<T, MT> { 066 @Autowired 067 private IRequestPartitionHelperSvc myRequestPartitionHelperSvc; 068 069 @Autowired 070 private IInterceptorBroadcaster myInterceptorBroadcaster; 071 072 @Description( 073 "Marks all currently existing resources of a given type, or all resources of all types, for reindexing.") 074 @Operation( 075 name = MARK_ALL_RESOURCES_FOR_REINDEXING, 076 idempotent = false, 077 returnParameters = {@OperationParam(name = "status")}) 078 /** 079 * @deprecated 080 * @see ReindexProvider#Reindex(List, IPrimitiveType, RequestDetails) 081 */ 082 @Deprecated 083 public IBaseResource markAllResourcesForReindexing( 084 @OperationParam(name = "type", min = 0, max = 1, typeName = "code") IPrimitiveType<String> theType) { 085 086 if (theType != null && isNotBlank(theType.getValueAsString())) { 087 getResourceReindexingSvc().markAllResourcesForReindexing(theType.getValueAsString()); 088 } else { 089 getResourceReindexingSvc().markAllResourcesForReindexing(); 090 } 091 092 IBaseParameters retVal = ParametersUtil.newInstance(getContext()); 093 094 IPrimitiveType<?> string = ParametersUtil.createString(getContext(), "Marked resources"); 095 ParametersUtil.addParameterToParameters(getContext(), retVal, "status", string); 096 097 return retVal; 098 } 099 100 @Description("Forces a single pass of the resource reindexing processor") 101 @Operation( 102 name = PERFORM_REINDEXING_PASS, 103 idempotent = false, 104 returnParameters = {@OperationParam(name = "status")}) 105 /** 106 * @deprecated 107 * @see ReindexProvider#Reindex(List, IPrimitiveType, RequestDetails) 108 */ 109 @Deprecated 110 public IBaseResource performReindexingPass() { 111 Integer count = getResourceReindexingSvc().runReindexingPass(); 112 113 IBaseParameters retVal = ParametersUtil.newInstance(getContext()); 114 115 IPrimitiveType<?> string; 116 if (count == null) { 117 string = ParametersUtil.createString(getContext(), "Index pass already proceeding"); 118 } else { 119 string = ParametersUtil.createString(getContext(), "Indexed " + count + " resources"); 120 } 121 ParametersUtil.addParameterToParameters(getContext(), retVal, "status", string); 122 123 return retVal; 124 } 125 126 @Operation(name = JpaConstants.OPERATION_GET_RESOURCE_COUNTS, idempotent = true) 127 @Description( 128 shortDefinition = 129 "Provides the number of resources currently stored on the server, broken down by resource type") 130 public IBaseParameters getResourceCounts() { 131 IBaseParameters retVal = ParametersUtil.newInstance(getContext()); 132 133 Map<String, Long> counts = getDao().getResourceCountsFromCache(); 134 counts = defaultIfNull(counts, Collections.emptyMap()); 135 counts = new TreeMap<>(counts); 136 for (Map.Entry<String, Long> nextEntry : counts.entrySet()) { 137 ParametersUtil.addParameterToParametersInteger( 138 getContext(), 139 retVal, 140 nextEntry.getKey(), 141 nextEntry.getValue().intValue()); 142 } 143 144 return retVal; 145 } 146 147 @Operation( 148 name = ProviderConstants.OPERATION_META, 149 idempotent = true, 150 returnParameters = {@OperationParam(name = "return", typeName = "Meta")}) 151 public IBaseParameters meta(RequestDetails theRequestDetails) { 152 IBaseParameters retVal = ParametersUtil.newInstance(getContext()); 153 ParametersUtil.addParameterToParameters( 154 getContext(), retVal, "return", getDao().metaGetOperation(theRequestDetails)); 155 return retVal; 156 } 157 158 @SuppressWarnings("unchecked") 159 @Transaction 160 public IBaseBundle transaction(RequestDetails theRequestDetails, @TransactionParam IBaseBundle theResources) { 161 startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest()); 162 try { 163 IFhirSystemDao<T, MT> dao = getDao(); 164 return (IBaseBundle) dao.transaction(theRequestDetails, (T) theResources); 165 } finally { 166 endRequest(((ServletRequestDetails) theRequestDetails).getServletRequest()); 167 } 168 } 169 170 @Operation(name = ProviderConstants.OPERATION_REPLACE_REFERENCES, global = true) 171 @Description( 172 value = 173 "This operation searches for all references matching the provided id and updates them to references to the provided target-reference-id.", 174 shortDefinition = "Repoints referencing resources to another resources instance") 175 public IBaseParameters replaceReferences( 176 @OperationParam( 177 name = ProviderConstants.OPERATION_REPLACE_REFERENCES_PARAM_SOURCE_REFERENCE_ID, 178 min = 1, 179 typeName = "string") 180 IPrimitiveType<String> theSourceId, 181 @OperationParam( 182 name = ProviderConstants.OPERATION_REPLACE_REFERENCES_PARAM_TARGET_REFERENCE_ID, 183 min = 1, 184 typeName = "string") 185 IPrimitiveType<String> theTargetId, 186 @OperationParam( 187 name = ProviderConstants.OPERATION_REPLACE_REFERENCES_RESOURCE_LIMIT, 188 typeName = "unsignedInt") 189 IPrimitiveType<Integer> theResourceLimit, 190 ServletRequestDetails theServletRequest) { 191 startRequest(theServletRequest); 192 193 try { 194 validateReplaceReferencesParams(theSourceId, theTargetId); 195 196 int resourceLimit = MergeResourceHelper.setResourceLimitFromParameter(myStorageSettings, theResourceLimit); 197 198 IdDt sourceId = new IdDt(theSourceId.getValue()); 199 IdDt targetId = new IdDt(theTargetId.getValue()); 200 RequestPartitionId partitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest( 201 theServletRequest, ReadPartitionIdRequestDetails.forRead(targetId)); 202 203 List<IProvenanceAgent> provenanceAgents = 204 ProvenanceAgentsPointcutUtil.ifHasCallHooks(theServletRequest, myInterceptorBroadcaster); 205 206 ReplaceReferencesRequest replaceReferencesRequest = new ReplaceReferencesRequest( 207 sourceId, targetId, resourceLimit, partitionId, true, provenanceAgents); 208 IBaseParameters retval = 209 getReplaceReferencesSvc().replaceReferences(replaceReferencesRequest, theServletRequest); 210 if (ParametersUtil.getNamedParameter(getContext(), retval, OPERATION_REPLACE_REFERENCES_OUTPUT_PARAM_TASK) 211 .isPresent()) { 212 HttpServletResponse response = theServletRequest.getServletResponse(); 213 response.setStatus(HttpServletResponse.SC_ACCEPTED); 214 } 215 return retval; 216 } finally { 217 endRequest(theServletRequest); 218 } 219 } 220 221 @Operation(name = OPERATION_UNDO_REPLACE_REFERENCES, global = true) 222 @Description( 223 value = 224 "This operation undoes the effects of a previous $hapi.fhir.replace-references operation by restoring " 225 + "references that were replaced from the target back to the original source.", 226 shortDefinition = 227 "Restores references from target back to source for resources that were previously updated by a $hapi.fhir.replace-references operation.") 228 public IBaseParameters undoReplaceReferences( 229 @OperationParam( 230 name = ProviderConstants.OPERATION_REPLACE_REFERENCES_PARAM_SOURCE_REFERENCE_ID, 231 min = 1, 232 typeName = "string") 233 IPrimitiveType<String> theSourceId, 234 @OperationParam( 235 name = ProviderConstants.OPERATION_REPLACE_REFERENCES_PARAM_TARGET_REFERENCE_ID, 236 min = 1, 237 typeName = "string") 238 IPrimitiveType<String> theTargetId, 239 ServletRequestDetails theRequestDetails) { 240 startRequest(theRequestDetails); 241 242 try { 243 244 validateReplaceReferencesParams(theSourceId, theTargetId); 245 246 IdDt sourceId = new IdDt(theSourceId.getValue()); 247 IdDt targetId = new IdDt(theTargetId.getValue()); 248 249 RequestPartitionId partitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest( 250 theRequestDetails, ReadPartitionIdRequestDetails.forRead(targetId)); 251 252 int resourceLimit = myStorageSettings.getInternalSynchronousSearchSize(); 253 254 UndoReplaceReferencesRequest undoReplaceReferencesRequest = 255 new UndoReplaceReferencesRequest(sourceId, targetId, partitionId, resourceLimit); 256 257 return getUndoReplaceReferencesSvc().undoReplaceReferences(undoReplaceReferencesRequest, theRequestDetails); 258 } finally { 259 endRequest(theRequestDetails); 260 } 261 } 262 263 private static void validateReplaceReferencesParams( 264 IPrimitiveType<String> theSourceId, IPrimitiveType<String> theTargetId) { 265 if (theSourceId == null || isBlank(theSourceId.getValue())) { 266 throw new InvalidRequestException(Msg.code(2583) + "Parameter '" 267 + OPERATION_REPLACE_REFERENCES_PARAM_SOURCE_REFERENCE_ID + "' is blank"); 268 } 269 270 if (theTargetId == null || isBlank(theTargetId.getValue())) { 271 throw new InvalidRequestException(Msg.code(2584) + "Parameter '" 272 + OPERATION_REPLACE_REFERENCES_PARAM_TARGET_REFERENCE_ID + "' is blank"); 273 } 274 } 275}