hapifhir / hapi-fhir

🔥 HAPI FHIR - Java API for HL7 FHIR Clients and Servers
http://hapifhir.io
Apache License 2.0
1.94k stars 1.3k forks source link

In hapi 5.7.0 setting setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID) in DaoConfig throws HAPI-2001 #3462

Open tyfoni-systematic opened 2 years ago

tyfoni-systematic commented 2 years ago

Describe the bug If the dao is configured to use resourceServerIdStrategy = DaoConfig.IdStrategyEnum.UUID, then the translateForcedIdToPids in IdHelperService fails because the findAndResolveByForcedIdWithNoType returns an empty collection.

It seems that the lookup may be done before the HFJ_FORCED_ID is populated.

To Reproduce Steps to reproduce the behavior:

  1. Configure the dao: daoConfig().setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID)
  2. Try creating a resource, eg. a CodeSystem
  3. See exception: ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException: HAPI-2001: Resource CodeSystem/1 is not known at ca.uhn.fhir.jpa.dao.index.IdHelperService.resolveResourceIdentity(IdHelperService.java:143) at ca.uhn.fhir.jpa.dao.index.IdHelperService.resolveResourcePersistentIds(IdHelperService.java:184) at ca.uhn.fhir.jpa.dao.index.IdHelperService.resolveResourcePersistentIds(IdHelperService.java:215) at ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl.getCodeSystemResourcePid(TermCodeSystemStorageSvcImpl.java:607)

Expected behavior That the resource is created and has an id with UUID

Screenshots N/A

Environment (please complete the following information):

Additional context

jkiddo commented 2 years ago

FYI - Bug is also triggered when installing IG's with DaoConfig.IdStrategyEnum.UUID enabled

rogtrifork commented 2 years ago

This seems to be caused due to the following:

Starting with 5.7.0, the flow of creating resources changed which seems to have broken CodeSystem objects specifically: In 5.7.0 ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao#doCreateForPost performs the creation in the following steps:

    protected DaoMethodOutcome doCreateForPost(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
                // .... 
                // 1) Calls update where it creates the Resource entry (hfj_resource table)
        ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, false, theTransactionDetails, false, thePerformIndexing);

        // 2) Create forcedId (hfj_forced_id)
        ResourcePersistentId persistentId = new ResourcePersistentId(updatedEntity.getResourceId());
        if (resourceHadIdBeforeStorage) {
            if (resourceIdWasServerAssigned) {
                boolean createForPureNumericIds = true;
                createForcedIdIfNeeded(entity, resourceIdBeforeStorage, createForPureNumericIds, persistentId, theRequestPartitionId);
            } else {
                boolean createForPureNumericIds = getConfig().getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC;
                createForcedIdIfNeeded(entity, resourceIdBeforeStorage, createForPureNumericIds, persistentId, theRequestPartitionId);
            }
}

While in 5.6.x ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao#doCreateForPost this seems to be reversed:

                // 1) Create forcedId (hfj_forced_id)
        boolean serverAssignedId;
        if (isNotBlank(theResource.getIdElement().getIdPart())) {
            if (theResource.getUserData(JpaConstants.RESOURCE_ID_SERVER_ASSIGNED) == Boolean.TRUE) {
                createForcedIdIfNeeded(entity, theResource.getIdElement(), true);
                serverAssignedId = true;
            } else {
                validateResourceIdCreation(theResource, theRequest);
                boolean createForPureNumericIds = getConfig().getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC;
                createForcedIdIfNeeded(entity, theResource.getIdElement(), createForPureNumericIds);
                serverAssignedId = false;
            }
        } else {
            serverAssignedId = true;
        }

        // ....

                 // 2) Create the Resource (hfj_resource table)
        ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, false, theTransactionDetails, false, thePerformIndexing);

While this does seem to work for other resources, for CodeSystem specifically, this seems to cause a problem further down the flow when it tries to perform ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoCodeSystemR4#updateEntity where it eventually expects the forcedId to be present already as can be seen in the join statements of ca.uhn.fhir.jpa.dao.data.IForcedIdDao#findAndResolveByForcedIdWithNoType but since the forcedId does not yet exist, findAndResolveByForcedIdWithNoType ends up returning an empty result.