Fix #1355 - Failed to call access method: java.lang.NullPointerException in case of bundle entry without resource

This commit is contained in:
jamesagnew 2019-06-22 15:51:56 -04:00
parent 2543f27697
commit 989e26fdb4
7 changed files with 134 additions and 17 deletions

View File

@ -96,6 +96,8 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully update
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingMandatoryResource=Missing required resource in Bundle.entry[{1}].resource for operation {0}
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}

View File

@ -246,16 +246,11 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder();
TransactionCallback<BUNDLE> callback = theStatus -> {
try {
BUNDLE subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode());
myVersionAdapter.addEntry(subRequestBundle, nextRequestEntry);
return processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request");
};
try {
// FIXME: this doesn't need to be a callback
BUNDLE nextResponseBundle = callback.doInTransaction(null);
BUNDLE nextResponseBundle = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request");
BUNDLEENTRY subResponseEntry = myVersionAdapter.getEntries(nextResponseBundle).get(0);
myVersionAdapter.addEntry(resp, subResponseEntry);
@ -362,7 +357,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
placeholderIds.add(fullUrl);
}
}
Collections.sort(entries, new TransactionSorter(placeholderIds));
entries.sort(new TransactionSorter(placeholderIds));
/*
* All of the write operations in the transaction (PUT, POST, etc.. basically anything
@ -638,6 +633,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
switch (verb) {
case "POST": {
// CREATE
validateResourcePresent(res, order, verb);
@SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
res.setId((String) null);
@ -695,6 +691,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
}
case "PUT": {
// UPDATE
validateResourcePresent(res, order, verb);
@SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
@ -908,6 +905,13 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
}
}
private void validateResourcePresent(IBaseResource theResource, Integer theOrder, String theVerb) {
if (theResource == null) {
String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingMandatoryResource", theVerb, theOrder);
throw new InvalidRequestException(msg);
}
}
private IIdType newIdType(String theResourceType, String theResourceId, String theVersion) {
org.hl7.fhir.r4.model.IdType id = new org.hl7.fhir.r4.model.IdType(theResourceType, theResourceId, theVersion);
return myContext.getVersion().newIdType().setValue(id.getValue());

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.codesystems.IssueType;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -65,7 +66,10 @@ public class TransactionProcessorVersionAdapterDstu3 implements TransactionProce
@Override
public void populateEntryWithOperationOutcome(BaseServerResponseException theCaughtEx, Bundle.BundleEntryComponent theEntry) {
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(OperationOutcome.IssueSeverity.ERROR).setDiagnostics(theCaughtEx.getMessage());
oo.addIssue()
.setSeverity(OperationOutcome.IssueSeverity.ERROR)
.setDiagnostics(theCaughtEx.getMessage())
.setCode(OperationOutcome.IssueType.EXCEPTION);
theEntry.getResponse().setOutcome(oo);
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao.r4;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -65,7 +65,10 @@ public class TransactionProcessorVersionAdapterR4 implements TransactionProcesso
@Override
public void populateEntryWithOperationOutcome(BaseServerResponseException theCaughtEx, Bundle.BundleEntryComponent theEntry) {
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(OperationOutcome.IssueSeverity.ERROR).setDiagnostics(theCaughtEx.getMessage());
oo.addIssue()
.setSeverity(OperationOutcome.IssueSeverity.ERROR)
.setDiagnostics(theCaughtEx.getMessage())
.setCode(OperationOutcome.IssueType.EXCEPTION);
theEntry.getResponse().setOutcome(oo);
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.data.*;
@ -9,7 +10,6 @@ import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
@ -17,7 +17,6 @@ import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry;
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
@ -32,9 +31,12 @@ import ca.uhn.fhir.rest.server.BasePagingProvider;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.*;
@ -42,6 +44,7 @@ import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@ -290,12 +293,12 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Autowired
protected ICacheWarmingSvc myCacheWarmingSvc;
@Autowired
private JpaValidationSupportChainR4 myJpaValidationSupportChainR4;
@Autowired
protected SubscriptionRegistry mySubscriptionRegistry;
@Autowired
private JpaValidationSupportChainR4 myJpaValidationSupportChainR4;
private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor;
private List<Object> mySystemInterceptors;
private FhirValidator myValidator;
@After()
public void afterCleanupDao() {
@ -388,6 +391,20 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
return newJsonParser.parseResource(type, string);
}
protected void validate(IBaseResource theResource) {
if (myValidator == null) {
myValidator = myFhirCtx.newValidator();
FhirInstanceValidator validator = new FhirInstanceValidator();
validator.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Ignore);
myValidator.registerValidatorModule(validator);
}
ValidationResult result = myValidator.validateWithResult(theResource);
if (!result.isSuccessful()) {
fail(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome()));
}
}
@AfterClass
public static void afterClassClearContextBaseJpaR4Test() throws Exception {
ourValueSetDao.purgeCaches();

View File

@ -14,9 +14,13 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Bundle.*;
@ -796,6 +800,83 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
}
}
@Test
public void testTransactionMissingResourceForPost() {
Bundle request = new Bundle();
request.setType(BundleType.TRANSACTION);
request
.addEntry()
.setFullUrl("Patient/")
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Patient/");
try {
mySystemDao.transaction(mySrd, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Missing required resource in Bundle.entry[0].resource for operation POST", e.getMessage());
}
}
@Test
public void testTransactionMissingResourceForPut() {
Bundle request = new Bundle();
request.setType(BundleType.TRANSACTION);
request
.addEntry()
.setFullUrl("Patient/123")
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Patient/123");
try {
mySystemDao.transaction(mySrd, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Missing required resource in Bundle.entry[0].resource for operation PUT", e.getMessage());
}
}
@Test
public void testBatchMissingResourceForPost() {
Bundle request = new Bundle();
request.setType(BundleType.BATCH);
request
.addEntry()
.setFullUrl("Patient/")
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Patient/");
Bundle outcome = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("400 Bad Request", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals(IssueSeverity.ERROR, ((OperationOutcome)outcome.getEntry().get(0).getResponse().getOutcome()).getIssueFirstRep().getSeverity());
assertEquals("Missing required resource in Bundle.entry[0].resource for operation POST", ((OperationOutcome)outcome.getEntry().get(0).getResponse().getOutcome()).getIssueFirstRep().getDiagnostics());
validate(outcome);
}
@Test
public void testBatchMissingResourceForPut() {
Bundle request = new Bundle();
request.setType(BundleType.BATCH);
request
.addEntry()
.setFullUrl("Patient/123")
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Patient/123");
Bundle outcome = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("400 Bad Request", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals(IssueSeverity.ERROR, ((OperationOutcome)outcome.getEntry().get(0).getResponse().getOutcome()).getIssueFirstRep().getSeverity());
assertEquals("Missing required resource in Bundle.entry[0].resource for operation PUT", ((OperationOutcome)outcome.getEntry().get(0).getResponse().getOutcome()).getIssueFirstRep().getDiagnostics());
validate(outcome);
}
@Test
public void testTransactionCreateInlineMatchUrlWithOneMatch() {
String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch";

View File

@ -100,6 +100,12 @@
Uploading the LOINC/RSNA Radiology Playbook would occasionally fail when evaluating part type names
due to case sensitivity. This has been corrected.
</action>
<action type="fix" issue="1355">
Invoking the transaction or batch operation on the JPA server would fail
with a NullPointerException if the Bundle passed in did not contain
a resource in an entry that required a resource (e.g. a POST). Thanks to
GitHub user @lytvynenko-dmitriy for reporting!
</action>
</release>
<release version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix">