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