This commit is contained in:
lmds1 2014-10-16 10:48:58 -04:00
commit a642745095
12 changed files with 296 additions and 61 deletions

View File

@ -24,7 +24,11 @@ public class GenericClientExample {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Perform a search
Bundle results = client.search().forResource(Patient.class).where(Patient.FAMILY.matches().value("duck")).execute();
Bundle results = client
.search()
.forResource(Patient.class)
.where(Patient.FAMILY.matches().value("duck"))
.execute();
System.out.println("Found " + results.size() + " patients named 'duck'");
// END SNIPPET: simple
@ -44,7 +48,12 @@ public class GenericClientExample {
// Invoke the server create method (and send pretty-printed JSON
// encoding to the server
// instead of the default which is non-pretty printed XML)
client.create().resource(patient).prettyPrint().encodedJson().execute();
client
.create()
.resource(patient)
.prettyPrint()
.encodedJson()
.execute();
// END SNIPPET: create
}
{
@ -63,7 +72,10 @@ public class GenericClientExample {
// Invoke the server create method (and send pretty-printed JSON
// encoding to the server
// instead of the default which is non-pretty printed XML)
client.update().resource(patient).execute();
client
.update()
.resource(patient)
.execute();
// END SNIPPET: update
}
{

View File

@ -104,6 +104,11 @@
Documentation on contained resources contained a typo and did not actually produce contained resources. Thanks
to David Hay of Orion Health for reporting!
</action>
<action type="fix" issue="33">
Server was incorrectly including contained resources being returned as both contained resources, and as
top-level resources in the returned bundle for search operations.
Thanks to Bill de Beaubien for reporting!
</action>
</release>
<release version="0.6" date="2014-Sep-08" description="This release brings a number of new features and bug fixes!">
<!--

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.parser;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
@ -55,6 +57,14 @@ public abstract class BaseParser implements IParser {
myContext = theContext;
}
protected String fixContainedResourceId(String theValue) {
if (StringUtils.isNotBlank(theValue)&&theValue.charAt(0)=='#') {
return theValue.substring(1);
}
return theValue;
}
private void containResourcesForEncoding(ContainedResources theContained, IResource theResource, IResource theTarget) {
List<ResourceReferenceDt> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, ResourceReferenceDt.class);
@ -70,7 +80,7 @@ public abstract class BaseParser implements IParser {
for (ResourceReferenceDt next : allElements) {
IResource resource = next.getResource();
if (resource != null) {
if (resource.getId().isEmpty()) {
if (resource.getId().isEmpty() || resource.getId().isLocal()) {
theContained.addContained(resource);
} else {
continue;
@ -191,6 +201,26 @@ public abstract class BaseParser implements IParser {
throw new DataFormatException(nextChild + " has no child of type " + type);
}
protected String determineReferenceText(ResourceReferenceDt theRef) {
String reference = theRef.getReference().getValue();
if (isBlank(reference)) {
if (theRef.getResource() != null) {
IdDt containedId = getContainedResources().getResourceId(theRef.getResource());
if (containedId != null && !containedId.isEmpty()) {
if (containedId.isLocal()) {
reference = containedId.getValue();
} else {
reference = "#" + containedId.getValue();
}
} else if (theRef.getResource().getId() != null && theRef.getResource().getId().hasIdPart()) {
reference = theRef.getResource().getId().getValue();
}
}
}
return reference;
}
static class ContainedResources {
private long myNextContainedId = 1;
@ -202,9 +232,14 @@ public abstract class BaseParser implements IParser {
return;
}
// TODO: make this configurable between the two below (and something else?)
IdDt newId = new IdDt(myNextContainedId++);
// newId = new IdDt(UUID.randomUUID().toString());
IdDt newId;
if (theResource.getId().isLocal()) {
newId = theResource.getId();
} else {
// TODO: make this configurable between the two below (and something else?)
// newId = new IdDt(UUID.randomUUID().toString());
newId = new IdDt(myNextContainedId++);
}
myResourceToId.put(theResource, newId);
myResources.add(theResource);

View File

@ -284,29 +284,13 @@ public class JsonParser extends BaseParser implements IParser {
}
case RESOURCE_REF: {
ResourceReferenceDt referenceDt = (ResourceReferenceDt) theValue;
IdDt value = referenceDt.getReference();
if (theChildName != null) {
theWriter.writeStartObject(theChildName);
} else {
theWriter.writeStartObject();
}
String reference = value.getValue();
if (StringUtils.isBlank(reference)) {
if (value.getResourceType() != null && StringUtils.isNotBlank(value.getIdPart())) {
reference = myContext.getResourceDefinition(value.getResourceType()).getName() + '/' + value.getIdPart();
}
}
if (StringUtils.isBlank(reference)) {
if (referenceDt.getResource() != null) {
IdDt containedId = getContainedResources().getResourceId(referenceDt.getResource());
if (containedId != null) {
reference = "#" + containedId.getValue();
} else if (referenceDt.getResource().getId() != null && referenceDt.getResource().getId().hasIdPart()) {
reference = referenceDt.getResource().getId().getValue();
}
}
}
String reference = determineReferenceText(referenceDt);
if (StringUtils.isNotBlank(reference)) {
theWriter.write(XmlParser.RESREF_REFERENCE, reference);
@ -321,11 +305,14 @@ public class JsonParser extends BaseParser implements IParser {
theWriter.writeStartArray(theChildName);
ContainedDt value = (ContainedDt) theValue;
for (IResource next : value.getContainedResources()) {
encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true);
if (getContainedResources().getResourceId(next)!=null) {
continue;
}
encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true, fixContainedResourceId(next.getId().getValue()));
}
for (IResource next : getContainedResources().getContainedResources()) {
IdDt resourceId = getContainedResources().getResourceId(next);
encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true, resourceId.getValue());
encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true, fixContainedResourceId(resourceId.getValue()));
}
theWriter.writeEnd();
break;
@ -424,6 +411,8 @@ public class JsonParser extends BaseParser implements IParser {
theEventWriter.writeStartArray(childName);
inArray = true;
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theIsSubElementWithinResource);
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theIsSubElementWithinResource) {
// suppress narratives from contained resources
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theIsSubElementWithinResource);
}

View File

@ -828,8 +828,12 @@ class ParserState<T> {
ourLog.debug("Discarding contained resource with no ID!");
} else {
getPreResourceState().getContainedResources().put(res.getId().getValueAsString(), res);
if (!res.getId().isLocal()) {
res.setId(new IdDt('#' + res.getId().getIdPart()));
}
}
getPreResourceState().getCurrentElement().getContained().getContainedResources().add(res);
}
}

View File

@ -467,11 +467,14 @@ public class XmlParser extends BaseParser implements IParser {
ContainedDt value = (ContainedDt) nextValue;
theEventWriter.writeStartElement("contained");
for (IResource next : value.getContainedResources()) {
encodeResourceToXmlStreamWriter(next, theEventWriter, true);
if (getContainedResources().getResourceId(next)!=null) {
continue;
}
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
}
for (IResource next : getContainedResources().getContainedResources()) {
IdDt resourceId = getContainedResources().getResourceId(next);
encodeResourceToXmlStreamWriter(next, theEventWriter, true, resourceId.getValue());
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()));
}
theEventWriter.writeEndElement();
break;
@ -494,6 +497,7 @@ public class XmlParser extends BaseParser implements IParser {
}
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter,
List<? extends BaseRuntimeChildDefinition> children, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
for (BaseRuntimeChildDefinition nextChild : children) {
@ -545,6 +549,8 @@ public class XmlParser extends BaseParser implements IParser {
theEventWriter.writeAttribute("url", extensionUrl);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childName, childDef, null, theIncludedResource);
theEventWriter.writeEndElement();
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theIncludedResource) {
// suppress narratives from contained resources
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theIncludedResource);
}
@ -569,23 +575,7 @@ public class XmlParser extends BaseParser implements IParser {
}
private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, ResourceReferenceDt theRef) throws XMLStreamException {
String reference = theRef.getReference().getValue();
// if (StringUtils.isBlank(reference)) {
// if (theRef.getResourceType() != null && StringUtils.isNotBlank(theRef.getResourceId())) {
// reference = myContext.getResourceDefinition(theRef.getResourceType()).getName() + '/' + theRef.getResourceId();
// }
// }
if (isBlank(reference)) {
if (theRef.getResource() != null) {
IdDt containedId = getContainedResources().getResourceId(theRef.getResource());
if (containedId != null) {
reference = "#" + containedId.getValue();
} else if (theRef.getResource().getId() != null && theRef.getResource().getId().hasIdPart()) {
reference = theRef.getResource().getId().getValue();
}
}
}
String reference = determineReferenceText(theRef);
if (StringUtils.isNotBlank(reference)) {
theEventWriter.writeStartElement(RESREF_REFERENCE);
@ -599,6 +589,7 @@ public class XmlParser extends BaseParser implements IParser {
}
}
private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
String resourceId = null;
if (theIncludedResource && StringUtils.isNotBlank(theResource.getId().getValue())) {

View File

@ -1035,6 +1035,13 @@ public class RestfulServer extends HttpServlet {
Set<IdDt> addedResourceIds = new HashSet<IdDt>();
for (IResource next : theResult) {
Set<String> containedIds = new HashSet<String>();
for (IResource nextContained : next.getContained().getContainedResources()) {
if (nextContained.getId().isEmpty()==false) {
containedIds.add(nextContained.getId().getValue());
}
}
if (theContext.getNarrativeGenerator() != null) {
String title = theContext.getNarrativeGenerator().generateTitle(next);
ourLog.trace("Narrative generator created title: {}", title);
@ -1053,6 +1060,11 @@ public class RestfulServer extends HttpServlet {
IResource nextRes = nextRef.getResource();
if (nextRes != null) {
if (nextRes.getId().hasIdPart()) {
if (containedIds.contains(nextRes.getId().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
IdDt id = nextRes.getId().toVersionless();
if (id.hasResourceType() == false) {
String resName = theContext.getResourceDefinition(nextRes).getName();

View File

@ -17,6 +17,15 @@ public class IdDtTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(IdDtTest.class);
@Test
public void testDetectLocal() {
IdDt id = new IdDt("#123");
assertEquals("#123", id.getValue());
assertTrue(id.isLocal());
}
@Test
public void testDetermineBase() {

View File

@ -463,7 +463,7 @@ public class JsonParserTest {
}
@Test
public void testEncodeContained() {
public void testEncodeContained__() {
// Create an organization
Organization org = new Organization();
org.getName().setValue("Contained Test Organization");
@ -489,10 +489,97 @@ public class JsonParserTest {
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\"", "resourceType\":\"Organization", "id\":\"1\"")));
assertThat(encoded, containsString("reference\":\"#1\""));
}
@Test
public void testEncodeContainedWithNarrativeIsSuppresed() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
// Create an organization, note that the organization does not have an ID
Organization org = new Organization();
org.getName().setValue("Contained Test Organization");
org.getText().setDiv("<div>FOOBAR</div>");
// Create a patient
Patient patient = new Patient();
patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345");
patient.getText().setDiv("<div>BARFOO</div>");
patient.getManagingOrganization().setResource(org);
String encoded = parser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, not(containsString("FOOBAR")));
assertThat(encoded, (containsString("BARFOO")));
}
@Test
public void testEncodeContained() {
IParser xmlParser = ourCtx.newJsonParser().setPrettyPrint(true);
// Create an organization, note that the organization does not have an ID
Organization org = new Organization();
org.getName().setValue("Contained Test Organization");
// Create a patient
Patient patient = new Patient();
patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345");
// Put the organization as a reference in the patient resource
patient.getManagingOrganization().setResource(org);
String encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\":[", "\"id\":\"1\"", "\"identifier\"", "\"reference\":\"#1\"")));
// Create a bundle with just the patient resource
List<IResource> resources = new ArrayList<IResource>();
resources.add(patient);
Bundle b = Bundle.withResources(resources, ourCtx, "http://example.com/base");
// Encode the bundle
encoded = xmlParser.encodeBundleToString(b);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\":[", "\"id\":\"1\"", "\"identifier\"", "\"reference\":\"#1\"")));
// Re-parse the bundle
patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReference().getValue());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();
assertEquals("#1", org.getId().getValue());
assertEquals("Contained Test Organization", org.getName().getValue());
// And re-encode a second time
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\":[", "\"id\":\"1\"", "\"identifier\"", "\"reference\":\"#1\"")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("\"contained\":", "[", "\"contained\":"))));
// And re-encode once more, with the references cleared
patient.getContained().getContainedResources().clear();
patient.getManagingOrganization().setReference((IdDt)null);
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\":[", "\"id\":\"1\"", "\"identifier\"", "\"reference\":\"#1\"")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("\"contained\":", "[", "\"contained\":"))));
// And re-encode once more, with the references cleared and a manually set local ID
patient.getContained().getContainedResources().clear();
patient.getManagingOrganization().setReference((IdDt)null);
patient.getManagingOrganization().getResource().setId(new IdDt("#333"));
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\":[", "\"id\":\"333\"", "\"identifier\"", "\"reference\":\"#333\"")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("\"contained\":", "[", "\"contained\":"))));
}
@Test
public void testEncodeContainedResources() throws IOException {
@ -539,7 +626,7 @@ public class JsonParserTest {
DiagnosticReport rpt = new DiagnosticReport();
Specimen spm = new Specimen();
spm.getText().setDiv("AAA");
rpt.getText().setDiv("AAA");
rpt.addSpecimen().setResource(spm);
IParser p = new FhirContext(DiagnosticReport.class).newJsonParser().setPrettyPrint(true);

View File

@ -1,9 +1,18 @@
package ca.uhn.fhir.parser;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.containsString;
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.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.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStreamReader;
@ -12,7 +21,6 @@ import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -115,9 +123,34 @@ public class XmlParserTest {
}
@Test
public void testEncodeContainedWithNarrativeIsSuppresed() {
IParser parser = ourCtx.newXmlParser().setPrettyPrint(true);
// Create an organization, note that the organization does not have an ID
Organization org = new Organization();
org.getName().setValue("Contained Test Organization");
org.getText().setDiv("<div>FOOBAR</div>");
// Create a patient
Patient patient = new Patient();
patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345");
patient.getText().setDiv("<div>BARFOO</div>");
patient.getManagingOrganization().setResource(org);
String encoded = parser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, not(containsString("FOOBAR")));
assertThat(encoded, (containsString("BARFOO")));
}
@Test
public void testEncodeContained() {
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
// Create an organization, note that the organization does not have an ID
Organization org = new Organization();
org.getName().setValue("Contained Test Organization");
@ -130,7 +163,7 @@ public class XmlParserTest {
// Put the organization as a reference in the patient resource
patient.getManagingOrganization().setResource(org);
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
String encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, containsString("<contained>"));
assertThat(encoded, containsString("<reference value=\"#1\"/>"));
@ -141,11 +174,46 @@ public class XmlParserTest {
Bundle b = Bundle.withResources(resources, ourCtx, "http://example.com/base");
// Encode the buntdle
encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
encoded = xmlParser.encodeBundleToString(b);
ourLog.info(encoded);
assertThat(encoded, containsString("<contained>"));
assertThat(encoded, stringContainsInOrder(Arrays.asList("<contained>","id=\"1\"", "</contained>")));
assertThat(encoded, containsString("<reference value=\"#1\"/>"));
assertThat(encoded, stringContainsInOrder(Arrays.asList("<entry>", "</entry>")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("<entry>", "</entry>", "<entry>"))));
// Re-parse the bundle
patient = (Patient) xmlParser.parseResource(xmlParser.encodeResourceToString(patient));
assertEquals("#1", patient.getManagingOrganization().getReference().getValue());
assertNotNull(patient.getManagingOrganization().getResource());
org = (Organization) patient.getManagingOrganization().getResource();
assertEquals("#1", org.getId().getValue());
assertEquals("Contained Test Organization", org.getName().getValue());
// And re-encode a second time
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("<contained>", "<Organization ", "id=\"1\"", "</Organization", "</contained>", "<reference value=\"#1\"/>")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("<contained>", "<Org", "<contained>"))));
assertThat(encoded, containsString("<reference value=\"#1\"/>"));
// And re-encode once more, with the references cleared
patient.getContained().getContainedResources().clear();
patient.getManagingOrganization().setReference((IdDt)null);
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("<contained>", "<Organization ", "id=\"1\"", "</Organization", "</contained>", "<reference value=\"#1\"/>")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("<contained>", "<Org", "<contained>"))));
assertThat(encoded, containsString("<reference value=\"#1\"/>"));
// And re-encode once more, with the references cleared and a manually set local ID
patient.getContained().getContainedResources().clear();
patient.getManagingOrganization().setReference((IdDt)null);
patient.getManagingOrganization().getResource().setId(new IdDt("#333"));
encoded = xmlParser.encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("<contained>", "<Organization ", "id=\"333\"", "</Organization", "</contained>", "<reference value=\"#333\"/>")));
assertThat(encoded, not(stringContainsInOrder(Arrays.asList("<contained>", "<Org", "<contained>"))));
}
@ -351,7 +419,8 @@ public class XmlParserTest {
DiagnosticReport rpt = new DiagnosticReport();
Specimen spm = new Specimen();
spm.getText().setDiv("AAA");
spm.addIdentifier("urn", "123");
rpt.getText().setDiv("AAA");
rpt.addSpecimen().setResource(spm);
IParser p = ourCtx.newXmlParser().setPrettyPrint(true);

View File

@ -2,7 +2,7 @@
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n
</pattern>
</encoder>
</appender>

View File

@ -174,6 +174,28 @@ public class CompleteResourceProviderTest {
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSearchByResourceChain01</td>"));
}
@Test
public void testSaveAndRetrieveWithContained() {
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveWithContained01");
Organization o1 = new Organization();
o1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveWithContained02");
p1.getManagingOrganization().setResource(o1);
IdDt newId = ourClient.create().resource(p1).execute().getId();
Patient actual = ourClient.read(Patient.class, newId);
assertEquals(1, actual.getContained().getContainedResources().size());
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSaveAndRetrieveWithContained01</td>"));
Bundle b = ourClient.search().forResource("Patient").where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system","testSaveAndRetrieveWithContained01")).execute();
assertEquals(1, b.size());
}
@Test
public void testSearchByIdentifier() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier01");