Merge remote-tracking branch 'remotes/origin/master' into im_20200728_term_multi_version_support

This commit is contained in:
ianmarshall 2020-08-25 12:07:13 -04:00
commit 796d2923e0
11 changed files with 132 additions and 39 deletions

View File

@ -567,6 +567,10 @@ public abstract class BaseClient implements IRestfulClient {
@Override @Override
public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
if (theResponseStatusCode == Constants.STATUS_HTTP_204_NO_CONTENT) {
return null;
}
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) { if (respType == null) {
if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) { if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) {

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2049
title: A potential NPE in the JPA server was fixed. This error could be triggered by searching for resources with
unresolvable references. Thanks to Anders Havn for the pull request!

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2050
title: An issue was fixed where multiple JPA server updates to the same resource within the same
database transaction would fail with a database constraint error.

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2051
title: The FHIR Client will now accept an HTTP 204 NO CONTENT as a response to a write operation such
as a create/update/transaction/etc.

View File

@ -1057,7 +1057,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
entity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue()); entity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue());
} }
newParams.setParamsOn(entity); newParams.populateResourceTableSearchParamsPresentFlags(entity);
entity.setIndexStatus(INDEX_STATUS_INDEXED); entity.setIndexStatus(INDEX_STATUS_INDEXED);
populateFullTextFields(myContext, theResource, entity); populateFullTextFields(myContext, theResource, entity);
} }
@ -1206,6 +1206,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// Synchronize search param indexes // Synchronize search param indexes
AddRemoveCount searchParamAddRemoveCount = myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, entity, existingParams); AddRemoveCount searchParamAddRemoveCount = myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, entity, existingParams);
newParams.populateResourceTableParamCollections(entity);
// Interceptor broadcast: JPA_PERFTRACE_INFO // Interceptor broadcast: JPA_PERFTRACE_INFO
if (!searchParamAddRemoveCount.isEmpty()) { if (!searchParamAddRemoveCount.isEmpty()) {
if (JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) { if (JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) {

View File

@ -745,6 +745,9 @@ public class SearchBuilder implements ISearchBuilder {
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition)); q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
List<Long> results = q.getResultList(); List<Long> results = q.getResultList();
for (Long resourceLink : results) { for (Long resourceLink : results) {
if (resourceLink == null) {
continue;
}
if (theReverseMode) { if (theReverseMode) {
pidsToInclude.add(new ResourcePersistentId(resourceLink)); pidsToInclude.add(new ResourcePersistentId(resourceLink));
} else { } else {

View File

@ -887,6 +887,30 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
} }
} }
@Test
public void testCreateThenUpdateInSameTransaction() {
Patient initialPatient = new Patient();
IIdType id = myPatientDao.create(initialPatient).getId().toUnqualifiedVersionless();
runInTransaction(() -> {
Patient p = new Patient();
p.setId(id);
p.setActive(true);
p.addName().setFamily("FAMILY");
myPatientDao.update(p);
p = new Patient();
p.setId(id);
p.setActive(false);
p.addName().setFamily("FAMILY2");
myPatientDao.update(p);
});
assertEquals(1, myPatientDao.search(SearchParameterMap.newSynchronous("name", new StringParam("family2"))).size());
assertEquals(1, myPatientDao.search(SearchParameterMap.newSynchronous("active", new TokenParam("false"))).size());
}
@Test @Test
public void testCreateSummaryFails() { public void testCreateSummaryFails() {
Patient p = new Patient(); Patient p = new Patient();

View File

@ -33,7 +33,6 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.BaseValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.BaseValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.HapiExtensions; import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.IValidatorModule;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
@ -76,6 +75,7 @@ import org.hl7.fhir.dstu3.model.DocumentReference;
import org.hl7.fhir.dstu3.model.Encounter; import org.hl7.fhir.dstu3.model.Encounter;
import org.hl7.fhir.dstu3.model.Encounter.EncounterLocationComponent; import org.hl7.fhir.dstu3.model.Encounter.EncounterLocationComponent;
import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus; import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus;
import org.hl7.fhir.dstu3.model.Enumerations;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.IdType;
@ -96,6 +96,7 @@ import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Period; import org.hl7.fhir.dstu3.model.Period;
import org.hl7.fhir.dstu3.model.PlanDefinition;
import org.hl7.fhir.dstu3.model.Practitioner; import org.hl7.fhir.dstu3.model.Practitioner;
import org.hl7.fhir.dstu3.model.ProcedureRequest; import org.hl7.fhir.dstu3.model.ProcedureRequest;
import org.hl7.fhir.dstu3.model.Quantity; import org.hl7.fhir.dstu3.model.Quantity;
@ -103,6 +104,7 @@ import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse; import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.RelatedArtifact;
import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription;
@ -112,7 +114,6 @@ import org.hl7.fhir.dstu3.model.UnsignedIntType;
import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
@ -138,6 +139,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -362,6 +364,45 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
} }
@Test
public void testSearchWithIncludeAllWithNotResolvableReference() {
// Arrange
myDaoConfig.setAllowExternalReferences(true);
Patient patient = new Patient();
patient.addName().setFamily(UUID.randomUUID().toString());
IIdType createdPatientId = ourClient.create().resource(patient).execute().getId();
RelatedArtifact relatedArtifactInternalReference = new RelatedArtifact();
relatedArtifactInternalReference.setDisplay(UUID.randomUUID().toString());
relatedArtifactInternalReference.setType(RelatedArtifact.RelatedArtifactType.PREDECESSOR);
relatedArtifactInternalReference.setResource(new Reference(createdPatientId.toUnqualifiedVersionless()));
RelatedArtifact relatedArtifactExternalReference = new RelatedArtifact();
relatedArtifactExternalReference.setDisplay(UUID.randomUUID().toString());
relatedArtifactExternalReference.setType(RelatedArtifact.RelatedArtifactType.PREDECESSOR);
relatedArtifactExternalReference.setResource(new Reference("http://not-local-host.dk/hapi-fhir-jpaserver/fhir/Patient/2"));
PlanDefinition planDefinition = new PlanDefinition();
planDefinition.setStatus(Enumerations.PublicationStatus.ACTIVE);
planDefinition.setName(UUID.randomUUID().toString());
planDefinition.setRelatedArtifact(Arrays.asList(relatedArtifactInternalReference, relatedArtifactExternalReference));
IIdType createdPlanDefinitionId = ourClient.create().resource(planDefinition).execute().getId();
// Act
Bundle returnedBundle = ourClient.search()
.forResource(PlanDefinition.class)
.include(PlanDefinition.INCLUDE_ALL)
.where(PlanDefinition.NAME.matches().value(planDefinition.getName()))
.returnBundle(Bundle.class)
.execute();
// Assert
assertEquals(returnedBundle.getEntry().size(), 2);
assertEquals(createdPlanDefinitionId, genResourcesOfType(returnedBundle, PlanDefinition.class).get(0).getIdElement());
assertEquals(createdPatientId, genResourcesOfType(returnedBundle, Patient.class).get(0).getIdElement());
}
@Test @Test
public void testBundleCreateWithTypeTransaction() throws Exception { public void testBundleCreateWithTypeTransaction() throws Exception {
IGenericClient client = ourClient; IGenericClient client = ourClient;

View File

@ -105,26 +105,30 @@ public final class ResourceIndexedSearchParams {
return myLinks; return myLinks;
} }
public void setParamsOn(ResourceTable theEntity) { public void populateResourceTableSearchParamsPresentFlags(ResourceTable theEntity) {
theEntity.setParamsString(myStringParams);
theEntity.setParamsStringPopulated(myStringParams.isEmpty() == false); theEntity.setParamsStringPopulated(myStringParams.isEmpty() == false);
theEntity.setParamsToken(myTokenParams);
theEntity.setParamsTokenPopulated(myTokenParams.isEmpty() == false); theEntity.setParamsTokenPopulated(myTokenParams.isEmpty() == false);
theEntity.setParamsNumber(myNumberParams);
theEntity.setParamsNumberPopulated(myNumberParams.isEmpty() == false); theEntity.setParamsNumberPopulated(myNumberParams.isEmpty() == false);
theEntity.setParamsQuantity(myQuantityParams);
theEntity.setParamsQuantityPopulated(myQuantityParams.isEmpty() == false); theEntity.setParamsQuantityPopulated(myQuantityParams.isEmpty() == false);
theEntity.setParamsDate(myDateParams);
theEntity.setParamsDatePopulated(myDateParams.isEmpty() == false); theEntity.setParamsDatePopulated(myDateParams.isEmpty() == false);
theEntity.setParamsUri(myUriParams);
theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false); theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false);
theEntity.setParamsCoords(myCoordsParams);
theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false); theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false);
theEntity.setParamsCompositeStringUniquePresent(myCompositeStringUniques.isEmpty() == false); theEntity.setParamsCompositeStringUniquePresent(myCompositeStringUniques.isEmpty() == false);
theEntity.setResourceLinks(myLinks);
theEntity.setHasLinks(myLinks.isEmpty() == false); theEntity.setHasLinks(myLinks.isEmpty() == false);
} }
public void populateResourceTableParamCollections(ResourceTable theEntity) {
theEntity.setParamsString(myStringParams);
theEntity.setParamsToken(myTokenParams);
theEntity.setParamsNumber(myNumberParams);
theEntity.setParamsQuantity(myQuantityParams);
theEntity.setParamsDate(myDateParams);
theEntity.setParamsUri(myUriParams);
theEntity.setParamsCoords(myCoordsParams);
theEntity.setResourceLinks(myLinks);
}
void setUpdatedTime(Date theUpdateTime) { void setUpdatedTime(Date theUpdateTime) {
setUpdatedTime(myStringParams, theUpdateTime); setUpdatedTime(myStringParams, theUpdateTime);
setUpdatedTime(myNumberParams, theUpdateTime); setUpdatedTime(myNumberParams, theUpdateTime);

View File

@ -2397,33 +2397,6 @@ public class GenericClientDstu2Test {
assertEquals("Patient/2/_history/2", response.getEntry().get(1).getResponse().getLocation()); assertEquals("Patient/2/_history/2", response.getEntry().get(1).getResponse().getLocation());
} }
@Test
public void testTransactionHandle204NoBody() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
ca.uhn.fhir.model.dstu2.resource.Bundle bundle = new Bundle();
bundle.setType(BundleTypeEnum.TRANSACTION);
ca.uhn.fhir.model.dstu2.resource.Bundle.Entry entry = bundle.addEntry();
entry.setResource(new Patient());
entry.getRequest().setMethod(HTTPVerbEnum.PUT);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_204_NO_CONTENT, ""));
when(myHttpResponse.getEntity() ).thenReturn(null);
try {
client.transaction().withBundle(bundle).execute();
fail("Should throw an exception");
} catch (NonFhirResponseException e) {
assertEquals(Constants.STATUS_HTTP_204_NO_CONTENT, e.getStatusCode());
}
}
@Test @Test
public void testUpdateConditional() throws Exception { public void testUpdateConditional() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.util.TransactionBuilder;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.helger.commons.io.stream.StringInputStream; import com.helger.commons.io.stream.StringInputStream;
@ -36,6 +37,7 @@ import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine; import org.apache.http.message.BasicStatusLine;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Binary;
import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.BooleanType;
@ -2106,6 +2108,31 @@ public class GenericClientR4Test extends BaseGenericClientR4Test {
} }
@Test
public void testTransactionWithResponseHttp204NoContent() throws IOException {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 204, "No Content"));
when(myHttpResponse.getEntity()).thenReturn(null);
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer<Header[]>() {
@Override
public Header[] answer(InvocationOnMock theInvocation) {
return new Header[0];
}
});
TransactionBuilder builder = new TransactionBuilder(ourCtx);
builder.addCreateEntry(new Patient().setActive(true));
IBaseBundle outcome = client.transaction().withBundle(builder.getBundle()).execute();
assertNull(outcome);
assertEquals("http://example.com/fhir", capt.getAllValues().get(0).getURI().toASCIIString());
}
@Test @Test
public void testUpdateById() throws Exception { public void testUpdateById() throws Exception {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();