Merge branch 'master' into 674_everything_improvements for #674

This commit is contained in:
James Agnew 2017-06-19 06:19:12 -04:00
commit cebe881a15
8 changed files with 261 additions and 9 deletions

View File

@ -0,0 +1,90 @@
package ca.uhn.fhir.rest.client.interceptor;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.rest.client.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
/**
* This interceptor adds an arbitrary header to requests made by this client. Both the
* header name and the header value are specified by the calling code.
*/
public class SimpleRequestHeaderInterceptor implements IClientInterceptor {
private String myHeaderName;
private String myHeaderValue;
/**
* Constructor
*/
public SimpleRequestHeaderInterceptor() {
this(null, null);
}
/**
* Constructor
*/
public SimpleRequestHeaderInterceptor(String theHeaderName, String theHeaderValue) {
super();
myHeaderName = theHeaderName;
myHeaderValue = theHeaderValue;
}
public String getHeaderName() {
return myHeaderName;
}
public String getHeaderValue() {
return myHeaderValue;
}
@Override
public void interceptRequest(IHttpRequest theRequest) {
if (isNotBlank(getHeaderName())) {
theRequest.addHeader(getHeaderName(), getHeaderValue());
}
}
@Override
public void interceptResponse(IHttpResponse theResponse) throws IOException {
// nothing
}
public void setHeaderName(String theHeaderName) {
myHeaderName = theHeaderName;
}
public void setHeaderValue(String theHeaderValue) {
myHeaderValue = theHeaderValue;
}
}

View File

@ -39,6 +39,7 @@ import java.util.Map.Entry;
import java.util.Set;
import javax.persistence.TypedQuery;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.NameValuePair;
import org.hl7.fhir.dstu3.model.Bundle;
@ -79,6 +80,7 @@ import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
@ -408,7 +410,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
String matchUrl = nextReqEntry.getRequest().getIfNoneExist();
outcome = resourceDao.create(res, matchUrl, false, theRequestDetails);
if (nextResourceId != null) {
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res);
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
}
entriesToProcess.put(nextRespEntry, outcome.getEntity());
if (outcome.getCreated() == false) {
@ -474,7 +476,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
}
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res);
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
entriesToProcess.put(nextRespEntry, outcome.getEntity());
break;
}
@ -646,10 +648,8 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
return response;
}
private static void handleTransactionCreateOrUpdateOutcome(Map<IdType, IdType> idSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, IdType nextResourceId, DaoMethodOutcome outcome,
BundleEntryComponent newEntry, String theResourceType, IBaseResource theRes) {
BundleEntryComponent newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) {
IdType newId = (IdType) outcome.getId().toUnqualifiedVersionless();
IdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) {
@ -668,6 +668,19 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
}
newEntry.getResponse().setLastModified(((Resource) theRes).getMeta().getLastUpdated());
if (theRequestDetails != null) {
if (outcome.getResource() != null) {
String prefer = theRequestDetails.getHeader(Constants.HEADER_PREFER);
PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(prefer);
if (preferReturn != null) {
if (preferReturn == PreferReturnEnum.REPRESENTATION) {
newEntry.setResource((Resource) outcome.getResource());
}
}
}
}
}
private static boolean isPlaceholder(IdType theId) {

View File

@ -1181,6 +1181,23 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
assertThat(patients, (hasItems(id1a, id1b)));
assertThat(patients, not(hasItems(id2)));
}
{
SearchParameterMap params = new SearchParameterMap();
params.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, beforeR2)));
List<IIdType> patients = toUnqualifiedVersionlessIds(myPatientDao.search(params));
assertThat(patients, not(hasItems(id1a, id1b)));
assertThat(patients, (hasItems(id2)));
}
{
SearchParameterMap params = new SearchParameterMap();
params.setLastUpdated(new DateRangeParam(new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, beforeR2)));
List<IIdType> patients = toUnqualifiedVersionlessIds(myPatientDao.search(params));
assertThat(patients, (hasItems(id1a, id1b)));
assertThat(patients, not(hasItems(id2)));
}
}
@SuppressWarnings("deprecation")

View File

@ -1370,6 +1370,53 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info(ids.toString());
}
@Test
public void testSearchByLastUpdated() throws Exception {
String methodName = "testSearchByLastUpdated";
Patient p = new Patient();
p.addName().setFamily(methodName+"1");
IIdType pid1 = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
Thread.sleep(10);
long time1 = System.currentTimeMillis();
Thread.sleep(10);
Patient p2 = new Patient();
p2.addName().setFamily(methodName+"2");
IIdType pid2 = ourClient.create().resource(p2).execute().getId().toUnqualifiedVersionless();
HttpGet get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=lt" + new InstantType(new Date(time1)).getValueAsString());
CloseableHttpResponse response = ourHttpClient.execute(get);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(response.getEntity().getContent());
ourLog.info(output);
List<IIdType> ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output));
ourLog.info(ids.toString());
assertThat(ids, containsInAnyOrder(pid1));
} finally {
response.close();
}
get = new HttpGet(ourServerBase + "/Patient?_lastUpdated=gt" + new InstantType(new Date(time1)).getValueAsString());
response = ourHttpClient.execute(get);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(response.getEntity().getContent());
ourLog.info(output);
List<IIdType> ids = toUnqualifiedVersionlessIds(myFhirCtx.newXmlParser().parseResource(Bundle.class, output));
ourLog.info(ids.toString());
assertThat(ids, containsInAnyOrder(pid2));
} finally {
response.close();
}
}
@Test
public void testEverythingPatientType() throws Exception {
String methodName = "testEverythingPatientType";

View File

@ -60,6 +60,7 @@ import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
@ -80,6 +81,7 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderDstu3Test.class);
private static Server ourServer;
private static String ourServerBase;
private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor;
@Test
public void testTransactionWithInlineConditionalUrl() throws Exception {
@ -237,10 +239,17 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
myRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
}
@Before
public void before() {
mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor();
ourClient.registerInterceptor(mySimpleHeaderInterceptor);
}
@SuppressWarnings("deprecation")
@After
public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
}
@SuppressWarnings("deprecation")
@ -632,6 +641,44 @@ public class SystemProviderDstu3Test extends BaseJpaDstu3Test {
assertEquals(0, respSub.getEntry().size());
}
@Test
public void testTransactionCreateWithPreferHeader() throws Exception {
Patient p = new Patient();
p.setActive(true);
Bundle req;
Bundle resp;
// No prefer header
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(null, resp.getEntry().get(0).getResource());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
// Prefer return=minimal
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER);
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_MINIMAL);
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(null, resp.getEntry().get(0).getResource());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
// Prefer return=representation
mySimpleHeaderInterceptor.setHeaderName(Constants.HEADER_PREFER);
mySimpleHeaderInterceptor.setHeaderValue(Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_REPRESENTATION);
req = new Bundle();
req.setType(BundleType.TRANSACTION);
req.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl("Patient");
resp = ourClient.transaction().withBundle(req).execute();
assertEquals(Patient.class, resp.getEntry().get(0).getResource().getClass());
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();

View File

@ -27,6 +27,7 @@ import org.apache.http.ProtocolVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
@ -60,6 +61,7 @@ import ca.uhn.fhir.parser.CustomTypeDstu3Test.MyCustomPatient;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.client.SearchClientDstu3Test.ILocationClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor;
@ -178,6 +180,8 @@ public class GenericClientDstu3Test {
assertThat(oo.getText().getDivAsString(), containsString("OK!"));
}
@Test
public void testPatchJsonByIdType() throws Exception {
OperationOutcome conf = new OperationOutcome();
@ -1191,6 +1195,36 @@ public class GenericClientDstu3Test {
// assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString());
}
@Test
public void testSearchWithNullParameters() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
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), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
DateTimeDt now = DateTimeDt.withCurrentTime();
String dateString = now.getValueAsString().substring(0, 10);
client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value((String)null))
.returnBundle(Bundle.class)
.execute();
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
@Test
public void testSearchByDate() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";

View File

@ -43,7 +43,6 @@ import ca.uhn.fhir.rest.annotation.Sort;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.TestUtil;

View File

@ -32,6 +32,11 @@
Fix HTTP 500 error in JPA server if a numeric search parameter was supplied with no value, e.g.
<![CDATA[<code>GET /Observation?value-quantity=</code>]]>
</action>
<action type="add">
JPA server transaction processing now honours the Prefer header and includes
created and updated resource bodies in the response bundle if it is set
appropriately.
</action>
<action type="add">
Optimize queries in JPA server remove a few redundant select columns when performing
searches. This provides a slight speed increase in some cases.