Server should include lastUpdated in search responses if one is supplied

by the implementation. Also automatically include in this in JPA server
This commit is contained in:
James Agnew 2015-06-22 16:27:08 -04:00
parent ccc49e975f
commit 70d4abdf06
18 changed files with 339 additions and 71 deletions

View File

@ -65,6 +65,16 @@
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<!-- put the default-jar in the none phase to skip it from being created -->
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -44,8 +44,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
@ -307,11 +309,14 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
if (getMethodReturnType() == MethodReturnTypeEnum.BUNDLE_RESOURCE) {
IBaseResource resource;
InstantDt lastUpdated;
if (resultObj instanceof IBundleProvider) {
IBundleProvider result = (IBundleProvider) resultObj;
resource = result.getResources(0, 1).get(0);
lastUpdated = result.getPublished();
} else {
resource = (IResource) resultObj;
lastUpdated = ResourceMetadataKeyEnum.UPDATED.get((IResource) resource);
}
/*
@ -319,7 +324,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
*/
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
bundleFactory.initializeWithBundleResource(resource);
bundleFactory.addRootPropertiesToBundle(null, theRequest.getFhirServerBase(), linkSelf, count, getResponseBundleType());
bundleFactory.addRootPropertiesToBundle(null, theRequest.getFhirServerBase(), linkSelf, count, getResponseBundleType(), lastUpdated);
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = theServer.getInterceptors().get(i);

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.PushbackReader;
@ -500,10 +501,17 @@ public class MethodUtil {
try {
headerDateValue = DateUtils.parseDate(headerValue);
if (resource instanceof IResource) {
InstantDt lmValue = new InstantDt(headerDateValue);
((IResource) resource).getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue);
IResource iResource = (IResource) resource;
InstantDt existing = ResourceMetadataKeyEnum.UPDATED.get(iResource);
if (existing == null || existing.isEmpty()) {
InstantDt lmValue = new InstantDt(headerDateValue);
iResource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue);
}
} else if (resource instanceof IAnyResource) {
((IAnyResource) resource).getMeta().setLastUpdated(headerDateValue);
IAnyResource anyResource = (IAnyResource) resource;
if (anyResource.getMeta().getLastUpdated() == null) {
anyResource.getMeta().setLastUpdated(headerDateValue);
}
}
} catch (Exception e) {
ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString());

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.server;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -42,6 +44,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -201,7 +204,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished());
if (theServer.getPagingProvider() != null) {
int limit;
@ -221,10 +224,14 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType) {
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, InstantDt theLastUpdated) {
if (myBundle.getAuthorName().isEmpty()) {
myBundle.getAuthorName().setValue(theAuthor);
}
if (myBundle.getUpdated().isEmpty() && isNotBlank(theLastUpdated.getValueAsString())) {
myBundle.getUpdated().setValueAsString(theLastUpdated.getValueAsString());
}
if (myBundle.getBundleId().isEmpty()) {
myBundle.getBundleId().setValue(UUID.randomUUID().toString());
@ -301,7 +308,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
List<IBaseResource> addedResourcesThisPass = new ArrayList<IBaseResource>();
for (BaseResourceReferenceDt nextRef : references) {
IBaseResource nextRefRes = (IBaseResource) nextRef.getResource();
IBaseResource nextRefRes = nextRef.getResource();
if (nextRefRes != null) {
if (nextRefRes.getIdElement().hasIdPart()) {
if (containedIds.contains(nextRefRes.getIdElement().getValue())) {

View File

@ -27,6 +27,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
/**
@ -37,7 +38,7 @@ public interface IVersionSpecificBundleFactory {
void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes);
void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType);
void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, InstantDt theLastUpdated);
void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint,
int theOffset, Integer theCount, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes);

View File

@ -0,0 +1,28 @@
package ca.uhn.fhir.model.api;
import static org.junit.Assert.*;
import java.util.Date;
import org.junit.Test;
import ca.uhn.fhir.model.primitive.InstantDt;
public class InstantDtTest {
@Test
public void testParseHandlesMillis() {
InstantDt dt = new InstantDt();
dt.setValueAsString("2015-06-22T15:44:32.831-04:00");
Date date = dt.getValue();
InstantDt dt2 = new InstantDt();
dt2.setValue(date);
dt2.setTimeZoneZulu(true);
String string = dt2.getValueAsString();
assertEquals("2015-06-22T19:44:32.831Z", string);
}
}

View File

@ -1,8 +1,6 @@
package ca.uhn.fhir.jpa.dao;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLNonTransientConnectionException;
import org.junit.AfterClass;

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@SuppressWarnings("unused")
public class FhirResourceDaoDstu1Test extends BaseJpaTest {
private static ClassPathXmlApplicationContext ourCtx;

View File

@ -75,7 +75,6 @@ import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;

View File

@ -5,10 +5,15 @@ import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.InputStream;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@ -28,9 +33,9 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.Observation;

View File

@ -83,6 +83,7 @@ import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
@ -808,9 +809,12 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
InstantDt updated = ResourceMetadataKeyEnum.UPDATED.get(found);
assertNotNull(updated);
assertNotNull(updated.getValue());
assertTrue(updated.getValue().after(before));
assertTrue(updated.getValue().before(after));
Date value = updated.getValue();
assertNotNull(value);
ourLog.info(value.getTime()+"");
ourLog.info(before.getTime()+"");
assertTrue(value.after(before));
assertTrue(value.before(after));
}
@ -1120,7 +1124,7 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
// ourClient.registerInterceptor(new LoggingInterceptor(true));
ourClient.registerInterceptor(new LoggingInterceptor(true));
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.rest.server.provider.dstu2;
import static org.apache.commons.lang3.StringUtils.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -99,15 +100,15 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
ourLog.trace("No narrative generator specified");
}
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
for (ResourceReferenceInfo nextRefInfo : references) {
if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes))
continue;
for (ResourceReferenceInfo nextRefInfo : references) {
if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes))
continue;
IResource nextRes = (IResource) nextRefInfo.getResourceReference().getResource();
IResource nextRes = (IResource) nextRefInfo.getResourceReference().getResource();
if (nextRes != null) {
if (nextRes.getId().hasIdPart()) {
if (containedIds.contains(nextRes.getId().getValue())) {
@ -130,12 +131,12 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
}
includedResources.addAll(addedResourcesThisPass);
includedResources.addAll(addedResourcesThisPass);
// Linked resources may themselves have linked resources
references = new ArrayList<ResourceReferenceInfo>();
references = new ArrayList<ResourceReferenceInfo>();
for (IResource iResource : addedResourcesThisPass) {
List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
references.addAll(newReferences);
}
} while (references.isEmpty() == false);
@ -157,17 +158,15 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType) {
@Override
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, InstantDt theLastUpdated) {
if (myBundle.getId().isEmpty()) {
myBundle.setId(UUID.randomUUID().toString());
}
if (ResourceMetadataKeyEnum.PUBLISHED.get(myBundle) == null) {
InstantDt published = new InstantDt();
published.setToCurrentTimeInLocalTimeZone();
ResourceMetadataKeyEnum.PUBLISHED.put(myBundle, published);
if (ResourceMetadataKeyEnum.UPDATED.get(myBundle) == null) {
ResourceMetadataKeyEnum.UPDATED.put(myBundle, theLastUpdated);
}
if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theCompleteUrl)) {
@ -197,13 +196,18 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes) {
public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl,
boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes) {
int numToReturn;
String searchId = null;
List<IBaseResource> resourceList;
if (theServer.getPagingProvider() == null) {
numToReturn = theResult.size();
resourceList = theResult.getResources(0, numToReturn);
if (numToReturn > 0) {
resourceList = theResult.getResources(0, numToReturn);
} else {
resourceList = Collections.emptyList();
}
RestfulServerUtils.validateResourceListNotNull(resourceList);
} else {
@ -246,7 +250,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished());
if (theServer.getPagingProvider() != null) {
int limit;
@ -255,11 +259,13 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
myBundle.addLink().setRelation(Constants.LINK_NEXT).setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint));
myBundle.addLink().setRelation(Constants.LINK_NEXT)
.setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - limit);
myBundle.addLink().setRelation(Constants.LINK_PREVIOUS).setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint));
myBundle.addLink().setRelation(Constants.LINK_PREVIOUS)
.setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint));
}
}
}
@ -276,7 +282,8 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResources, String theServerBase, String theCompleteUrl, int theTotalResults, BundleTypeEnum theBundleType) {
public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResources, String theServerBase, String theCompleteUrl, int theTotalResults,
BundleTypeEnum theBundleType) {
myBundle = new Bundle();
myBundle.setId(UUID.randomUUID().toString());
@ -289,7 +296,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
if (theBundleType.equals(BundleTypeEnum.TRANSACTION)) {
for (IBaseResource nextBaseRes : theResources) {
IResource next = (IResource)nextBaseRes;
IResource next = (IResource) nextBaseRes;
Entry nextEntry = myBundle.addEntry();
nextEntry.setResource(next);
@ -323,7 +330,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
for (IBaseResource nextBaseRes : theResult) {
IResource next = (IResource)nextBaseRes;
IResource next = (IResource) nextBaseRes;
Set<String> containedIds = new HashSet<String>();
for (IResource nextContained : next.getContained().getContainedResources()) {
if (nextContained.getId().isEmpty() == false) {
@ -401,7 +408,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
public List<IBaseResource> toListOfResources() {
ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
for (Entry next : myBundle.getEntry()) {
if (next.getResource()!=null) {
if (next.getResource() != null) {
retVal.add(next.getResource());
} else if (next.getTransactionResponse().getLocationElement().isEmpty() == false) {
IdDt id = new IdDt(next.getTransactionResponse().getLocation());

View File

@ -1,7 +1,16 @@
package ca.uhn.fhir.parser;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.StringReader;
import java.util.ArrayList;
@ -57,7 +66,6 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
public class XmlParserDstu2Test {
private static final FhirContext ourCtx = FhirContext.forDstu2();
@ -95,6 +103,30 @@ public class XmlParserDstu2Test {
}
@Test
public void testParseMetaUpdatedDate() {
//@formatter:off
String input = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"e2ee823b-ee4d-472d-b79d-495c23f16b99\"/>\n" +
" <meta>\n" +
" <lastUpdated value=\"2015-06-22T15:48:57.554-04:00\"/>\n" +
" </meta>\n" +
" <type value=\"searchset\"/>\n" +
" <base value=\"http://localhost:58109/fhir/context\"/>\n" +
" <total value=\"0\"/>\n" +
" <link>\n" +
" <relation value=\"self\"/>\n" +
" <url value=\"http://localhost:58109/fhir/context/Patient?_pretty=true\"/>\n" +
" </link>\n" +
"</Bundle>";
//@formatter:on
ca.uhn.fhir.model.dstu2.resource.Bundle b = ourCtx.newXmlParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, input);
InstantDt updated = ResourceMetadataKeyEnum.UPDATED.get(b);
assertEquals("2015-06-22T15:48:57.554-04:00", updated.getValueAsString());
}
@Test
public void testContainedResourceInExtensionUndeclared() {
Patient p = new Patient();

View File

@ -1,8 +1,12 @@
package ca.uhn.fhir.rest.client;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
@ -13,6 +17,7 @@ import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.Header;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
@ -34,6 +39,7 @@ import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
@ -258,6 +264,56 @@ public class GenericClientDstu2Test {
idx++;
}
@Test
public void testReadUpdatedHeaderDoesntOverwriteResourceValue() throws Exception {
//@formatter:off
final String input = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"e2ee823b-ee4d-472d-b79d-495c23f16b99\"/>\n" +
" <meta>\n" +
" <lastUpdated value=\"2015-06-22T15:48:57.554-04:00\"/>\n" +
" </meta>\n" +
" <type value=\"searchset\"/>\n" +
" <base value=\"http://localhost:58109/fhir/context\"/>\n" +
" <total value=\"0\"/>\n" +
" <link>\n" +
" <relation value=\"self\"/>\n" +
" <url value=\"http://localhost:58109/fhir/context/Patient?_pretty=true\"/>\n" +
" </link>\n" +
"</Bundle>";
//@formatter:on
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_XML + "; charset=UTF-8"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {
new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Sat, 20 Jun 2015 19:32:17 GMT")
});
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(input), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
ca.uhn.fhir.model.dstu2.resource.Bundle response;
//@formatter:off
response = client
.search()
.forResource(Patient.class)
.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
//@formatter:on
assertEquals("2015-06-22T15:48:57.554-04:00", ResourceMetadataKeyEnum.UPDATED.get(response).getValueAsString());
}
@Test
public void testOperationAsGetWithInParameters() throws Exception {
IParser p = ourCtx.newXmlParser();

View File

@ -2,10 +2,12 @@ package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
@ -17,6 +19,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@ -25,6 +28,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.util.PatternMatcher;
import ca.uhn.fhir.util.PortUtil;
@ -39,6 +43,8 @@ public class SearchDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchDstu2Test.class);
private static int ourPort;
private static InstantDt ourReturnPublished;
private static Server ourServer;
@Test
@ -58,18 +64,6 @@ public class SearchDstu2Test {
assertNull(status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION));
}
@Test
public void testResultBundleHasUuid() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithRef");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, PatternMatcher.pattern("id value..[0-9a-f-]+\\\""));
}
@Test
public void testEncodeConvertsReferencesToRelativeJson() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithRef&_format=json");
@ -87,6 +81,31 @@ public class SearchDstu2Test {
assertNull(status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION));
}
@Test
public void testResultBundleHasUuid() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithRef");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, PatternMatcher.pattern("id value..[0-9a-f-]+\\\""));
}
@Test
public void testResultBundleHasUpdateTime() throws Exception {
ourReturnPublished = new InstantDt("2011-02-03T11:22:33Z");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithBundleProvider&_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertThat(responseContent, stringContainsInOrder("<lastUpdated value=\"2011-02-03T11:22:33Z\"/>"));
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
@ -115,12 +134,43 @@ public class SearchDstu2Test {
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@Search(queryName="searchWithBundleProvider")
public IBundleProvider searchWithBundleProvider() {
return new IBundleProvider() {
@Override
public InstantDt getPublished() {
return ourReturnPublished;
}
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
throw new IllegalStateException();
}
@Override
public Integer preferredPageSize() {
return null;
}
@Override
public int size() {
return 0;
}
};
}
@Search(queryName="searchWithRef")
public Patient searchWithRef() {
Patient patient = new Patient();
@ -129,11 +179,6 @@ public class SearchDstu2Test {
return patient;
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
}
}

View File

@ -157,14 +157,17 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType) {
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, InstantDt theLastUpdated) {
if (myBundle.getId().isEmpty()) {
myBundle.setId(UUID.randomUUID().toString());
}
InstantDt published = new InstantDt();
published.setToCurrentTimeInLocalTimeZone();
if (myBundle.getMeta().getLastUpdated() == null) {
InstantType instantType = new InstantType();
instantType.setValueAsString(theLastUpdated.getValueAsString());
myBundle.getMeta().setLastUpdatedElement(instantType);
}
if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theCompleteUrl)) {
myBundle.addLink().setRelation("self").setUrl(theCompleteUrl);
@ -243,7 +246,7 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory {
}
addResourcesToBundle(resourceList, theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished());
if (theServer.getPagingProvider() != null) {
int limit;

View File

@ -16,6 +16,7 @@ import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.Header;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
@ -59,10 +60,61 @@ public class GenericClientDstu2Hl7OrgTest {
public void before() {
myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
ourCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
}
@Test
public void testReadUpdatedHeaderDoesntOverwriteResourceValue() throws Exception {
//@formatter:off
final String input = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"e2ee823b-ee4d-472d-b79d-495c23f16b99\"/>\n" +
" <meta>\n" +
" <lastUpdated value=\"2015-06-22T15:48:57.554-04:00\"/>\n" +
" </meta>\n" +
" <type value=\"searchset\"/>\n" +
" <base value=\"http://localhost:58109/fhir/context\"/>\n" +
" <total value=\"0\"/>\n" +
" <link>\n" +
" <relation value=\"self\"/>\n" +
" <url value=\"http://localhost:58109/fhir/context/Patient?_pretty=true\"/>\n" +
" </link>\n" +
"</Bundle>";
//@formatter:on
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_XML + "; charset=UTF-8"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {
new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Sat, 20 Jun 2015 19:32:17 GMT")
});
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(input), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
org.hl7.fhir.instance.model.Bundle response;
//@formatter:off
response = client
.search()
.forResource(Patient.class)
.returnBundle(org.hl7.fhir.instance.model.Bundle.class)
.execute();
//@formatter:on
assertEquals("2015-06-22T15:48:57.554-04:00", response.getMeta().getLastUpdatedElement().getValueAsString());
}
@SuppressWarnings("unused")
@Test
public void testSearchWithReverseInclude() throws Exception {

View File

@ -123,6 +123,13 @@
JPA server now supports sorting on reference parameters. Thanks to
Vishal Kachroo for reporting that this wasn't working!
</action>
<action type="fix">
Prevent Last-Updated header in responses coming back to the client from
overwriting the 'lastUpdated' value in the meta element in DSTU2
resources. This is important because 'lastUpdated' can have more
precision than the equivalent header, but the client previously
gave the header priority.
</action>
</release>
<release version="1.0" date="2015-May-8">
<action type="add">