Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2019-04-28 11:17:18 -04:00
commit ddf12e7c28
9 changed files with 187 additions and 45 deletions

View File

@ -11,9 +11,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
@ -50,7 +51,7 @@ public class BundleUtil {
BaseRuntimeChildDefinition relChild = relDef.getChildByName("relation"); BaseRuntimeChildDefinition relChild = relDef.getChildByName("relation");
List<IBase> relValues = relChild.getAccessor().getValues(nextLink); List<IBase> relValues = relChild.getAccessor().getValues(nextLink);
for (IBase next : relValues) { for (IBase next : relValues) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>)next; IPrimitiveType<?> nextValue = (IPrimitiveType<?>) next;
if (theLinkRelation.equals(nextValue.getValueAsString())) { if (theLinkRelation.equals(nextValue.getValueAsString())) {
isRightRel = true; isRightRel = true;
} }
@ -64,7 +65,7 @@ public class BundleUtil {
BaseRuntimeChildDefinition urlChild = linkDef.getChildByName("url"); BaseRuntimeChildDefinition urlChild = linkDef.getChildByName("url");
List<IBase> values = urlChild.getAccessor().getValues(nextLink); List<IBase> values = urlChild.getAccessor().getValues(nextLink);
for (IBase nextUrl : values) { for (IBase nextUrl : values) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>)nextUrl; IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextUrl;
if (isNotBlank(nextValue.getValueAsString())) { if (isNotBlank(nextValue.getValueAsString())) {
return nextValue.getValueAsString(); return nextValue.getValueAsString();
} }
@ -83,35 +84,35 @@ public class BundleUtil {
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request"); BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestDef = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request"); BaseRuntimeElementCompositeDefinition<?> requestDef = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url"); BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url");
List<Pair<String, IBaseResource>> retVal = new ArrayList<>(entries.size()); List<Pair<String, IBaseResource>> retVal = new ArrayList<>(entries.size());
for (IBase nextEntry : entries) { for (IBase nextEntry : entries) {
String url = null; String url = null;
IBaseResource resource = null; IBaseResource resource = null;
for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) { for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) {
for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) { for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) {
url = ((IPrimitiveType<String>)nextUrlValue).getValue(); url = ((IPrimitiveType<String>) nextUrlValue).getValue();
} }
} }
// Should return 0..1 only // Should return 0..1 only
for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) { for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) nextValue; resource = (IBaseResource) nextValue;
} }
retVal.add(Pair.of(url, resource)); retVal.add(Pair.of(url, resource));
} }
return retVal; return retVal;
} }
public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) { public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("type"); BaseRuntimeChildDefinition entryChild = def.getChildByName("type");
@ -147,13 +148,13 @@ public class BundleUtil {
List<IBase> entries = entryChild.getAccessor().getValues(theBundle); List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry"); BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request"); BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestElem = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request"); BaseRuntimeElementCompositeDefinition<?> requestElem = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
BaseRuntimeChildDefinition urlChild = requestElem.getChildByName("url"); BaseRuntimeChildDefinition urlChild = requestElem.getChildByName("url");
BaseRuntimeChildDefinition methodChild = requestElem.getChildByName("method"); BaseRuntimeChildDefinition methodChild = requestElem.getChildByName("method");
for (IBase nextEntry : entries) { for (IBase nextEntry : entries) {
IBaseResource resource = null; IBaseResource resource = null;
String url = null; String url = null;
@ -164,39 +165,40 @@ public class BundleUtil {
} }
for (IBase nextRequest : requestChild.getAccessor().getValues(nextEntry)) { for (IBase nextRequest : requestChild.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : urlChild.getAccessor().getValues(nextRequest)) { for (IBase nextUrl : urlChild.getAccessor().getValues(nextRequest)) {
url = ((IPrimitiveType<?>)nextUrl).getValueAsString(); url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
} }
for (IBase nextUrl : methodChild.getAccessor().getValues(nextRequest)) { for (IBase nextUrl : methodChild.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>)nextUrl).getValueAsString(); String methodString = ((IPrimitiveType<?>) nextUrl).getValueAsString();
if (isNotBlank(methodString)) { if (isNotBlank(methodString)) {
requestType = RequestTypeEnum.valueOf(methodString); requestType = RequestTypeEnum.valueOf(methodString);
} }
} }
} }
/* /*
* All 3 might be null - That's ok because we still want to know the * All 3 might be null - That's ok because we still want to know the
* order in the original bundle. * order in the original bundle.
*/ */
retVal.add(new BundleEntryParts(requestType, url, resource)); retVal.add(new BundleEntryParts(requestType, url, resource));
} }
return retVal; return retVal;
} }
/** /**
* Extract all of the resources from a given bundle * Extract all of the resources from a given bundle
*/ */
public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) { public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
return toListOfResourcesOfType(theContext, theBundle, null); return toListOfResourcesOfType(theContext, theBundle, IBaseResource.class);
} }
/** /**
* Extract all of the resources of a given type from a given bundle * Extract all of the resources of a given type from a given bundle
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T extends IBaseResource> List<T> toListOfResourcesOfType(FhirContext theContext, IBaseBundle theBundle, Class<T> theTypeToInclude) { public static <T extends IBaseResource> List<T> toListOfResourcesOfType(FhirContext theContext, IBaseBundle theBundle, Class<T> theTypeToInclude) {
Objects.requireNonNull(theTypeToInclude, "ResourceType must not be null");
List<T> retVal = new ArrayList<>(); List<T> retVal = new ArrayList<>();
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
@ -207,36 +209,36 @@ public class BundleUtil {
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
for (IBase nextEntry : entries) { for (IBase nextEntry : entries) {
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) { for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
if (theTypeToInclude != null && !theTypeToInclude.isAssignableFrom(next.getClass())) { if (theTypeToInclude.isAssignableFrom(next.getClass())) {
continue; retVal.add((T) next);
} }
retVal.add((T) next);
} }
} }
return retVal; return retVal;
} }
public static class BundleEntryParts public static class BundleEntryParts {
{
private final RequestTypeEnum myRequestType; private final RequestTypeEnum myRequestType;
private final IBaseResource myResource; private final IBaseResource myResource;
private final String myUrl; private final String myUrl;
BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource) { BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource) {
super(); super();
myRequestType = theRequestType; myRequestType = theRequestType;
myUrl = theUrl; myUrl = theUrl;
myResource = theResource; myResource = theResource;
} }
public RequestTypeEnum getRequestType() { public RequestTypeEnum getRequestType() {
return myRequestType; return myRequestType;
} }
public IBaseResource getResource() { public IBaseResource getResource() {
return myResource; return myResource;
} }
public String getUrl() { public String getUrl() {
return myUrl; return myUrl;
} }
} }
} }

View File

@ -114,3 +114,5 @@ ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.matchesFound=Matches fo
ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.noMatchesFound=No matches found! ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.noMatchesFound=No matches found!
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoConceptMapR4.matchesFound=Matches found! ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoConceptMapR4.matchesFound=Matches found!
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoConceptMapR4.noMatchesFound=No matches found! ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoConceptMapR4.noMatchesFound=No matches found!
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}

View File

@ -529,6 +529,12 @@
<artifactId>guava-testlib</artifactId> <artifactId>guava-testlib</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>16.0.3</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>

View File

@ -21,6 +21,9 @@ package ca.uhn.fhir.jpa.util.jsonpatch;
*/ */
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
@ -29,13 +32,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.JsonPatch; import com.github.fge.jsonpatch.JsonPatch;
import com.github.fge.jsonpatch.JsonPatchException; import com.github.fge.jsonpatch.JsonPatchException;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.intellij.lang.annotations.Language;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader;
import static org.apache.commons.lang3.StringUtils.defaultString;
public class JsonPatchUtils { public class JsonPatchUtils {
public static <T extends IBaseResource> T apply(FhirContext theCtx, T theResourceToUpdate, String thePatchBody) { public static <T extends IBaseResource> T apply(FhirContext theCtx, T theResourceToUpdate, @Language("JSON") String thePatchBody) {
// Parse the patch // Parse the patch
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION, false); mapper.configure(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION, false);
@ -54,7 +59,21 @@ public class JsonPatchUtils {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) theResourceToUpdate.getClass(); Class<T> clazz = (Class<T>) theResourceToUpdate.getClass();
T retVal = theCtx.newJsonParser().parseResource(clazz, mapper.writeValueAsString(after)); String postPatchedContent = mapper.writeValueAsString(after);
IParser fhirJsonParser = theCtx.newJsonParser();
fhirJsonParser.setParserErrorHandler(new StrictErrorHandler());
T retVal;
try {
retVal = fhirJsonParser.parseResource(clazz, postPatchedContent);
} catch (DataFormatException e) {
String resourceId = theResourceToUpdate.getIdElement().toUnqualifiedVersionless().getValue();
String resourceType = theCtx.getResourceDefinition(theResourceToUpdate).getName();
resourceId = defaultString(resourceId, resourceType);
String msg = theCtx.getLocalizer().getMessage(JsonPatchUtils.class, "failedToApplyPatch", resourceId, e.getMessage());
throw new InvalidRequestException(msg);
}
return retVal; return retVal;
} catch (IOException | JsonPatchException theE) { } catch (IOException | JsonPatchException theE) {

View File

@ -7,10 +7,14 @@ import org.apache.http.client.methods.HttpPatch;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Media;
import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
@ -19,6 +23,51 @@ import static org.junit.Assert.assertThat;
public class PatchProviderR4Test extends BaseResourceProviderR4Test { public class PatchProviderR4Test extends BaseResourceProviderR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(PatchProviderR4Test.class);
@Test
public void testPatchAddArray() throws IOException {
IIdType id;
{
Media media = new Media();
media.setId("465eb73a-bce3-423a-b86e-5d0d267638f4");
media.setDuration(100L);
myMediaDao.update(media);
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:system").setValue("0");
id = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
String patchText = "[ " +
" {" +
" \"op\": \"add\"," +
" \"path\": \"/derivedFrom\"," +
" \"value\": [" +
" {\"reference\": \"/Media/465eb73a-bce3-423a-b86e-5d0d267638f4\"}" +
" ]" +
" } " +
"]";
HttpPatch patch = new HttpPatch(ourServerBase + "/Observation/" + id.getIdPart());
patch.setEntity(new StringEntity(patchText, ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
patch.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION);
patch.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON);
CloseableHttpResponse response = ourHttpClient.execute(patch);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response:\n{}", responseString);
assertThat(responseString, containsString("\"derivedFrom\":[{\"reference\":\"Media/465eb73a-bce3-423a-b86e-5d0d267638f4\"}]"));
} finally {
response.close();
}
}
@Test @Test
public void testPatchUsingJsonPatch() throws Exception { public void testPatchUsingJsonPatch() throws Exception {
String methodName = "testPatchUsingJsonPatch"; String methodName = "testPatchUsingJsonPatch";
@ -186,5 +235,4 @@ public class PatchProviderR4Test extends BaseResourceProviderR4Test {
} }
} }

View File

@ -11,8 +11,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat; import static org.junit.Assert.*;
import static org.junit.Assert.fail;
public class JsonPatchUtilsTest extends BaseJpaTest { public class JsonPatchUtilsTest extends BaseJpaTest {
@ -48,12 +47,12 @@ public class JsonPatchUtilsTest extends BaseJpaTest {
public void testInvalidPatchSyntaxError() { public void testInvalidPatchSyntaxError() {
// Quotes are incorrect in the "value" body // Quotes are incorrect in the "value" body
String patchText = "[ {\n" + String patchText = "[ {" +
" \"comment\": \"add image to examination\",\n" + " \"comment\": \"add image to examination\"," +
" \"patch\": [ {\n" + " \"patch\": [ {" +
" \"op\": \"foo\",\n" + " \"op\": \"foo\"," +
" \"path\": \"/derivedFrom/-\",\n" + " \"path\": \"/derivedFrom/-\"," +
" \"value\": [{\"reference\": \"/Media/465eb73a-bce3-423a-b86e-5d0d267638f4\"}]\n" + " \"value\": [{\"reference\": \"/Media/465eb73a-bce3-423a-b86e-5d0d267638f4\"}]" +
" } ]\n" + " } ]\n" +
" } ]"; " } ]";
@ -69,6 +68,52 @@ public class JsonPatchUtilsTest extends BaseJpaTest {
} }
@Test
public void testPatchAddArray() {
String patchText = "[ " +
" {" +
" \"op\": \"add\"," +
" \"path\": \"/derivedFrom\"," +
" \"value\": [" +
" {\"reference\": \"/Media/465eb73a-bce3-423a-b86e-5d0d267638f4\"}" +
" ]" +
" } " +
"]";
Observation toUpdate = new Observation();
toUpdate = JsonPatchUtils.apply(ourCtx, toUpdate, patchText);
String outcome = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(toUpdate);
ourLog.info(outcome);
assertThat(outcome, containsString("\"reference\": \"Media/465eb73a-bce3-423a-b86e-5d0d267638f4\""));
}
@Test
public void testPatchAddInvalidElement() {
String patchText = "[ " +
" {" +
" \"op\": \"add\"," +
" \"path\": \"/derivedFromXXX\"," +
" \"value\": [" +
" {\"reference\": \"/Media/465eb73a-bce3-423a-b86e-5d0d267638f4\"}" +
" ]" +
" } " +
"]";
Observation toUpdate = new Observation();
try {
JsonPatchUtils.apply(ourCtx, toUpdate, patchText);
fail();
} catch (InvalidRequestException e) {
assertEquals("Failed to apply JSON patch to Observation: Unknown element 'derivedFromXXX' found during parse", e.getMessage());
}
}
@Override @Override
protected FhirContext getContext() { protected FhirContext getContext() {
return ourCtx; return ourCtx;

View File

@ -2,10 +2,13 @@ package ca.uhn.fhir.util;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.List;
public class BundleUtilTest { public class BundleUtilTest {
private static FhirContext ourCtx = FhirContext.forR4(); private static FhirContext ourCtx = FhirContext.forR4();
@ -38,6 +41,17 @@ public class BundleUtilTest {
Assert.assertEquals(null, BundleUtil.getTotal(ourCtx, b)); Assert.assertEquals(null, BundleUtil.getTotal(ourCtx, b));
} }
@Test
public void toListOfResourcesOfTypeTest() {
Bundle bundle = new Bundle();
for (int i = 0; i < 5; i++) {
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient()));
}
List<Patient> list = BundleUtil.toListOfResourcesOfType(ourCtx, bundle, Patient.class);
Assert.assertEquals(5, list.size());
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -544,7 +544,8 @@
<jaxb_core_version>2.3.0.1</jaxb_core_version> <jaxb_core_version>2.3.0.1</jaxb_core_version>
<jaxb_runtime_version>2.3.1</jaxb_runtime_version> <jaxb_runtime_version>2.3.1</jaxb_runtime_version>
<jersey_version>2.25.1</jersey_version> <jersey_version>2.25.1</jersey_version>
<jetty_version>9.4.17.v20190418</jetty_version> <!-- 9.4.17 seems to have issues -->
<jetty_version>9.4.14.v20181114</jetty_version>
<jsr305_version>3.0.2</jsr305_version> <jsr305_version>3.0.2</jsr305_version>
<!--<hibernate_version>5.2.10.Final</hibernate_version>--> <!--<hibernate_version>5.2.10.Final</hibernate_version>-->
<hibernate_version>5.4.2.Final</hibernate_version> <hibernate_version>5.4.2.Final</hibernate_version>

View File

@ -19,7 +19,7 @@
<li>Spring-Data (JPA): 2.1.3.RELEASE -&gt; 2.1.6.RELEASE</li> <li>Spring-Data (JPA): 2.1.3.RELEASE -&gt; 2.1.6.RELEASE</li>
<li>Caffeine (JPA): 2.6.2 -&gt; 2.7.0</li> <li>Caffeine (JPA): 2.6.2 -&gt; 2.7.0</li>
<li>JANSI (CLI): 1.16 -&gt; 1.17.1</li> <li>JANSI (CLI): 1.16 -&gt; 1.17.1</li>
<li>Jetty (CLI): 9.4.14.v20181114 -&gt; 9.4.17.v20190418</li> <!--<li>Jetty (CLI): 9.4.14.v20181114 -&gt; 9.4.17.v20190418</li>-->
</ul> </ul>
]]> ]]>
</action> </action>
@ -181,6 +181,11 @@
<action type="fix"> <action type="fix">
Ensure that database cursors are closed immediately after performing a FHIR search. Ensure that database cursors are closed immediately after performing a FHIR search.
</action> </action>
<action type="add">
When performing a JSON Patch in JPA server, the post-patched document is now validated to
ensure that the patch was valid for the candidate resource. This means that invalid patches
are caught and not just silently ignored.
</action>
<action type="add"> <action type="add">
Expunges are now done in batches in multiple threads. Both the number of expunge threads and batch size are configurable Expunges are now done in batches in multiple threads. Both the number of expunge threads and batch size are configurable
in DaoConfig. in DaoConfig.