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