Clean up encoding of contained resources so that resources are not

modified as a part of the encoding
This commit is contained in:
James Agnew 2014-10-14 14:20:19 -04:00
parent 507ea9bedf
commit 2a9d92df7a
13 changed files with 507 additions and 176 deletions

View File

@ -96,6 +96,14 @@
have also caused resource validation to fail occasionally on these platforms as well.
Thanks to Bill de Beaubien for reporting!
</action>
<action type="fix">
toString() method on TokenParam was incorrectly showing the system as the value.
Thanks to Bill de Beaubien for reporting!
</action>
<action type="update">
Documentation on contained resources contained a typo and did not actually produce contained resources. Thanks
to David Hay of Orion Health 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

@ -201,6 +201,9 @@ public class FhirContext {
* Create and return a new JSON parser.
*
* <p>
* Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or every message being parsed/encoded.
* </p>
* <p>
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed without incurring any performance penalty
* </p>
*/
@ -261,6 +264,9 @@ public class FhirContext {
* Create and return a new XML parser.
*
* <p>
* Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or every message being parsed/encoded.
* </p>
* <p>
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed without incurring any performance penalty
* </p>
*/

View File

@ -25,7 +25,9 @@ import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
@ -45,29 +47,60 @@ import ca.uhn.fhir.model.primitive.IdDt;
public abstract class BaseParser implements IParser {
private boolean mySuppressNarratives;
private ContainedResources myContainedResources;
private FhirContext myContext;
private boolean mySuppressNarratives;
public BaseParser(FhirContext theContext) {
myContext = theContext;
}
@Override
public TagList parseTagList(String theString) {
return parseTagList(new StringReader(theString));
private void containResourcesForEncoding(ContainedResources theContained, IResource theResource, IResource theTarget) {
List<ResourceReferenceDt> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, ResourceReferenceDt.class);
Set<String> allIds = new HashSet<String>();
for (IResource next : theTarget.getContained().getContainedResources()) {
String nextId = next.getId().getValue();
if (StringUtils.isNotBlank(nextId)) {
allIds.add(nextId);
}
}
@SuppressWarnings("cast")
@Override
public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) {
StringReader reader = new StringReader(theMessageString);
return (T) parseResource(theResourceType, reader);
for (ResourceReferenceDt next : allElements) {
IResource resource = next.getResource();
if (resource != null) {
if (resource.getId().isEmpty()) {
theContained.addContained(resource);
} else {
continue;
}
containResourcesForEncoding(theContained, resource, theTarget);
}
}
}
public void containResourcesForEncoding(IResource theResource) {
ContainedResources contained = new ContainedResources();
containResourcesForEncoding(contained, theResource, theResource);
myContainedResources = contained;
}
@Override
public Bundle parseBundle(String theXml) throws ConfigurationException, DataFormatException {
StringReader reader = new StringReader(theXml);
return parseBundle(reader);
public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
if (theBundle == null) {
throw new NullPointerException("Bundle can not be null");
}
StringWriter stringWriter = new StringWriter();
try {
encodeBundleToWriter(theBundle, stringWriter);
} catch (IOException e) {
throw new Error("Encountered IOException during write to string - This should not happen!");
}
return stringWriter.toString();
}
@Override
@ -92,24 +125,15 @@ public abstract class BaseParser implements IParser {
return stringWriter.toString();
}
@Override
public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
if (theBundle == null) {
throw new NullPointerException("Bundle can not be null");
}
StringWriter stringWriter = new StringWriter();
try {
encodeBundleToWriter(theBundle, stringWriter);
} catch (IOException e) {
throw new Error("Encountered IOException during write to string - This should not happen!");
ContainedResources getContainedResources() {
return myContainedResources;
}
return stringWriter.toString();
}
@Override
public IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
return parseResource(null, theMessageString);
/**
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded values.
*/
public boolean getSuppressNarratives() {
return mySuppressNarratives;
}
@Override
@ -117,49 +141,38 @@ public abstract class BaseParser implements IParser {
return parseBundle(null, theReader);
}
@Override
public Bundle parseBundle(String theXml) throws ConfigurationException, DataFormatException {
StringReader reader = new StringReader(theXml);
return parseBundle(reader);
}
@SuppressWarnings("cast")
@Override
public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) {
StringReader reader = new StringReader(theMessageString);
return (T) parseResource(theResourceType, reader);
}
@Override
public IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException {
return parseResource(null, theReader);
}
public void containResourcesForEncoding(IResource theResource) {
containResourcesForEncoding(theResource, theResource);
@Override
public IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
return parseResource(null, theMessageString);
}
private long myNextContainedId = 1;
private void containResourcesForEncoding(IResource theResource, IResource theTarget) {
List<ResourceReferenceDt> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, ResourceReferenceDt.class);
Set<String> allIds = new HashSet<String>();
for (IResource next : theTarget.getContained().getContainedResources()) {
String nextId = next.getId().getValue();
if (StringUtils.isNotBlank(nextId)) {
allIds.add(nextId);
}
}
for (ResourceReferenceDt next : allElements) {
IResource resource = next.getResource();
if (resource != null) {
if (resource.getId().isEmpty()) { // TODO: make this configurable between the two below (and something else?)
resource.setId(new IdDt(myNextContainedId++));
// resource.setId(new IdDt(UUID.randomUUID().toString()));
}
String nextResourceId = resource.getId().getValue();
if (!allIds.contains(nextResourceId)) {
theTarget.getContained().getContainedResources().add(resource);
allIds.add(resource.getId().getValue());
}
next.setReference("#" + resource.getId().getValue());
containResourcesForEncoding(resource, theTarget);
}
@Override
public TagList parseTagList(String theString) {
return parseTagList(new StringReader(theString));
}
@Override
public IParser setSuppressNarratives(boolean theSuppressNarratives) {
mySuppressNarratives = theSuppressNarratives;
return this;
}
protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IElement> type) {
@ -178,18 +191,37 @@ public abstract class BaseParser implements IParser {
throw new DataFormatException(nextChild + " has no child of type " + type);
}
@Override
public IParser setSuppressNarratives(boolean theSuppressNarratives) {
mySuppressNarratives = theSuppressNarratives;
return this;
static class ContainedResources {
private long myNextContainedId = 1;
private IdentityHashMap<IResource, IdDt> myResourceToId = new IdentityHashMap<IResource, IdDt>();
private List<IResource> myResources = new ArrayList<IResource>();
public void addContained(IResource theResource) {
if (myResourceToId.containsKey(theResource)) {
return;
}
// TODO: make this configurable between the two below (and something else?)
IdDt newId = new IdDt(myNextContainedId++);
// newId = new IdDt(UUID.randomUUID().toString());
myResourceToId.put(theResource, newId);
myResources.add(theResource);
}
public List<IResource> getContainedResources() {
return myResources;
}
public IdDt getResourceId(IResource theResource) {
return myResourceToId.get(theResource);
}
public boolean isEmpty() {
return myResourceToId.isEmpty();
}
/**
* If set to <code>true</code> (default is <code>false</code>), narratives
* will not be included in the encoded values.
*/
public boolean getSuppressNarratives() {
return mySuppressNarratives;
}
}

View File

@ -29,6 +29,12 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
/**
*
* <p>
* Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or every message being parsed/encoded.
* </p>
*/
public interface IParser {
String encodeBundleToString(Bundle theBundle) throws DataFormatException;
@ -40,26 +46,24 @@ public interface IParser {
void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException, DataFormatException;
/**
* Encodes a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
* Encodes a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</a>.
*
* @param theTagList The tag list to encode. Must not be null.
* @param theTagList
* The tag list to encode. Must not be null.
* @return An encoded tag list
*/
String encodeTagListToString(TagList theTagList);
/**
* Encodes a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
* Encodes a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</a>.
*
* @param theTagList The tag list to encode. Must not be null.
* @param theWriter The writer to encode to
* @param theTagList
* The tag list to encode. Must not be null.
* @param theWriter
* The writer to encode to
*/
void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException;
<T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader);
Bundle parseBundle(Reader theReader);
@ -70,15 +74,12 @@ public interface IParser {
* Parses a resource
*
* @param theResourceType
* The resource type to use. This can be used to explicitly
* specify a class which extends a built-in type (e.g. a custom
* type extending the default Patient class)
* The resource type to use. This can be used to explicitly specify a class which extends a built-in type (e.g. a custom type extending the default Patient class)
* @param theReader
* The reader to parse input from. Note that the Reader will not be closed by the parser upon completion.
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not
* recognized or invalid for any reason
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
<T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
@ -86,15 +87,12 @@ public interface IParser {
* Parses a resource
*
* @param theResourceType
* The resource type to use. This can be used to explicitly
* specify a class which extends a built-in type (e.g. a custom
* type extending the default Patient class)
* The resource type to use. This can be used to explicitly specify a class which extends a built-in type (e.g. a custom type extending the default Patient class)
* @param theString
* The string to parse
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not
* recognized or invalid for any reason
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
<T extends IResource> T parseResource(Class<T> theResourceType, String theString) throws DataFormatException;
@ -105,8 +103,7 @@ public interface IParser {
* The reader to parse input from. Note that the Reader will not be closed by the parser upon completion.
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not
* recognized or invalid for any reason
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException;
@ -117,15 +114,12 @@ public interface IParser {
* The string to parse
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not
* recognized or invalid for any reason
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException;
/**
* Parses a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
* Parses a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</a>.
*
* @param theReader
* A reader which will supply a tag list
@ -134,9 +128,7 @@ public interface IParser {
TagList parseTagList(Reader theReader);
/**
* Parses a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
* Parses a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</a>.
*
* @param theString
* A string containing a tag list
@ -145,20 +137,16 @@ public interface IParser {
TagList parseTagList(String theString);
/**
* Sets the "pretty print" flag, meaning that the parser will encode
* resources with human-readable spacing and newlines between elements
* instead of condensing output as much as possible.
* Sets the "pretty print" flag, meaning that the parser will encode resources with human-readable spacing and newlines between elements instead of condensing output as much as possible.
*
* @param thePrettyPrint
* The flag
* @return Returns an instance of <code>this</code> parser so that method
* calls can be conveniently chained
* @return Returns an instance of <code>this</code> parser so that method calls can be conveniently chained
*/
IParser setPrettyPrint(boolean thePrettyPrint);
/**
* If set to <code>true</code> (default is <code>false</code>), narratives
* will not be included in the encoded values.
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded values.
*/
IParser setSuppressNarratives(boolean theSuppressNarratives);

View File

@ -230,7 +230,7 @@ public class JsonParser extends BaseParser implements IParser {
}
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef,
String theChildName) throws IOException {
String theChildName, boolean theIsSubElementWithinResource) throws IOException {
switch (theChildDef.getChildType()) {
case PRIMITIVE_DATATYPE: {
@ -278,7 +278,7 @@ public class JsonParser extends BaseParser implements IParser {
if (theValue instanceof ExtensionDt) {
theWriter.write("url", ((ExtensionDt) theValue).getUrlAsString());
}
encodeCompositeElementToStreamWriter(theResDef, theResource, theValue, theWriter, childCompositeDef);
encodeCompositeElementToStreamWriter(theResDef, theResource, theValue, theWriter, childCompositeDef, theIsSubElementWithinResource);
theWriter.writeEnd();
break;
}
@ -297,6 +297,16 @@ public class JsonParser extends BaseParser implements IParser {
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();
}
}
}
if (StringUtils.isNotBlank(reference)) {
theWriter.write(XmlParser.RESREF_REFERENCE, reference);
@ -313,6 +323,10 @@ public class JsonParser extends BaseParser implements IParser {
for (IResource next : value.getContainedResources()) {
encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true);
}
for (IResource next : getContainedResources().getContainedResources()) {
IdDt resourceId = getContainedResources().getResourceId(next);
encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true, resourceId.getValue());
}
theWriter.writeEnd();
break;
}
@ -341,7 +355,7 @@ public class JsonParser extends BaseParser implements IParser {
}
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter,
List<? extends BaseRuntimeChildDefinition> theChildren) throws IOException {
List<? extends BaseRuntimeChildDefinition> theChildren, boolean theIsSubElementWithinResource) throws IOException {
for (BaseRuntimeChildDefinition nextChild : theChildren) {
if (nextChild instanceof RuntimeChildNarrativeDefinition) {
INarrativeGenerator gen = myContext.getNarrativeGenerator();
@ -351,7 +365,7 @@ public class JsonParser extends BaseParser implements IParser {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theIsSubElementWithinResource);
continue;
}
}
@ -371,8 +385,14 @@ public class JsonParser extends BaseParser implements IParser {
int valueIdx = 0;
for (IElement nextValue : values) {
if (nextValue == null || nextValue.isEmpty()) {
if (nextValue instanceof ContainedDt) {
if (theIsSubElementWithinResource || getContainedResources().isEmpty()) {
continue;
}
} else {
continue;
}
}
Class<? extends IElement> type = nextValue.getClass();
String childName = nextChild.getChildNameByDatatype(type);
@ -382,6 +402,10 @@ public class JsonParser extends BaseParser implements IParser {
}
boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
if (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES && theIsSubElementWithinResource) {
continue;
}
if (nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
// Don't encode extensions
// RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild;
@ -399,13 +423,13 @@ public class JsonParser extends BaseParser implements IParser {
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
theEventWriter.writeStartArray(childName);
inArray = true;
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theIsSubElementWithinResource);
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theIsSubElementWithinResource);
}
currentChildName = childName;
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theIsSubElementWithinResource);
}
if (nextValue instanceof ISupportsUndeclaredExtensions && primitive) {
@ -453,14 +477,24 @@ public class JsonParser extends BaseParser implements IParser {
}
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter,
BaseRuntimeElementCompositeDefinition<?> resDef) throws IOException, DataFormatException {
BaseRuntimeElementCompositeDefinition<?> resDef, boolean theIsSubElementWithinResource) throws IOException, DataFormatException {
extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, resDef, theResDef, theResource);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions());
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren());
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIsSubElementWithinResource);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren(),theIsSubElementWithinResource);
}
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull,
boolean theIsSubElementWithinResource) throws IOException {
String resourceId = null;
if (theIsSubElementWithinResource && StringUtils.isNotBlank(theResource.getId().getValue())) {
resourceId = theResource.getId().getValue();
}
encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theIsSubElementWithinResource, resourceId);
}
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, boolean theIsSubElementWithinResource,
String theResourceId) throws IOException {
if (!theIsSubElementWithinResource) {
super.containResourcesForEncoding(theResource);
}
@ -474,8 +508,8 @@ public class JsonParser extends BaseParser implements IParser {
}
theEventWriter.write("resourceType", resDef.getName());
if (theIsSubElementWithinResource && theResource.getId() != null && isNotBlank(theResource.getId().getValue())) {
theEventWriter.write("id", theResource.getId().getValue());
if (theResourceId != null) {
theEventWriter.write("id", theResourceId);
}
if (theResource instanceof Binary) {
@ -483,7 +517,7 @@ public class JsonParser extends BaseParser implements IParser {
theEventWriter.write("contentType", bin.getContentType());
theEventWriter.write("content", bin.getContentAsBase64());
} else {
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, resDef);
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, resDef, theIsSubElementWithinResource);
}
theEventWriter.writeEnd();
}
@ -972,7 +1006,7 @@ public class JsonParser extends BaseParser implements IParser {
// theEventWriter, myValue, def, "value" +
// WordUtils.capitalize(def.getName()));
String childName = myDef.getChildNameByDatatype(myValue.getClass());
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName,false);
}
// theEventWriter.name(myUndeclaredExtension.get);
@ -1003,7 +1037,7 @@ public class JsonParser extends BaseParser implements IParser {
throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName());
}
BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName,true);
}
// theEventWriter.name(myUndeclaredExtension.get);

View File

@ -34,8 +34,6 @@ import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
@ -52,6 +50,7 @@ import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
@ -214,7 +213,6 @@ public class XmlParser extends BaseParser implements IParser {
eventWriter.writeEndElement();
}
eventWriter.writeEndElement(); // entry
}
@ -426,8 +424,12 @@ public class XmlParser extends BaseParser implements IParser {
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName,
BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
if (nextValue.isEmpty()) {
if (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES && getContainedResources().isEmpty()==false && theIncludedResource == false) {
// We still want to go in..
} else {
return;
}
}
switch (childDef.getChildType()) {
case PRIMITIVE_DATATYPE: {
@ -467,6 +469,10 @@ public class XmlParser extends BaseParser implements IParser {
for (IResource next : value.getContainedResources()) {
encodeResourceToXmlStreamWriter(next, theEventWriter, true);
}
for (IResource next : getContainedResources().getContainedResources()) {
IdDt resourceId = getContainedResources().getResourceId(next);
encodeResourceToXmlStreamWriter(next, theEventWriter, true, resourceId.getValue());
}
theEventWriter.writeEndElement();
break;
}
@ -512,7 +518,7 @@ public class XmlParser extends BaseParser implements IParser {
}
for (IElement nextValue : values) {
if (nextValue == null || nextValue.isEmpty()) {
if ((nextValue == null || nextValue.isEmpty()) && !(nextValue instanceof ContainedDt)) {
continue;
}
Class<? extends IElement> type = nextValue.getClass();
@ -570,6 +576,17 @@ public class XmlParser extends BaseParser implements IParser {
// }
// }
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();
}
}
}
if (StringUtils.isNotBlank(reference)) {
theEventWriter.writeStartElement(RESREF_REFERENCE);
theEventWriter.writeAttribute("value", reference);
@ -582,12 +599,16 @@ public class XmlParser extends BaseParser implements IParser {
}
}
/**
* @param theIncludedResource
* Set to true only if this resource is an "included" resource, as opposed to a "root level" resource by itself or in a bundle entry
*
*/
private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
String resourceId = null;
if (theIncludedResource && StringUtils.isNotBlank(theResource.getId().getValue())) {
resourceId = theResource.getId().getValue();
}
encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId);
}
private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, String theResourceId) throws XMLStreamException {
if (!theIncludedResource) {
super.containResourcesForEncoding(theResource);
}
@ -600,8 +621,8 @@ public class XmlParser extends BaseParser implements IParser {
theEventWriter.writeStartElement(resDef.getName());
theEventWriter.writeDefaultNamespace(FHIR_NS);
if (theIncludedResource && StringUtils.isNotBlank(theResource.getId().getValue())) {
theEventWriter.writeAttribute("id", theResource.getId().getValue());
if (theResourceId != null) {
theEventWriter.writeAttribute("id", theResourceId);
}
if (theResource instanceof Binary) {

View File

@ -159,13 +159,13 @@ public class TokenParam extends BaseParam implements IQueryParameterType {
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
builder.append("system", defaultString(getValue()));
builder.append("system", defaultString(getSystem()));
builder.append("value", getValue());
if (myText) {
builder.append("text", myText);
builder.append(":text", myText);
}
if (getMissing() != null) {
builder.append("missing", getMissing());
builder.append(":missing", getMissing());
}
return builder.toString();
}

View File

@ -72,13 +72,15 @@ public class UnprocessableEntityException extends BaseServerResponseException {
}
private static OperationOutcome toOperationOutcome(String... theMessage) {
OperationOutcome operationOutcome = new OperationOutcome();
OperationOutcome OperationOutcome = new OperationOutcome();
if (theMessage != null) {
for (String next : theMessage) {
operationOutcome.addIssue().setDetails(next);
OperationOutcome.addIssue().setDetails(next);
}
}
return operationOutcome;
return OperationOutcome;
}
}

View File

@ -103,31 +103,109 @@ patient.getManagingOrganization().setReference("Organization/124362");]]></sourc
<p>
In the following example, the Organization resource has an ID set, so it will not
be contained but will rather appear as a distinct entry in any returned
bundles.
bundles. Both resources are added to a bundle, which will then have
two entries:
</p>
<source><![CDATA[Organization org = new Organization();
<source><![CDATA[// Create an organization
Organization org = new Organization();
org.setId("Organization/65546");
org.getName().setValue("Contained Test Organization");
// Create a patient
Patient patient = new Patient();
patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345");
patient.getManagingOrganization().setResource(patient);]]></source>
patient.getManagingOrganization().setResource(org);
// Create a list containing both resources. In a server method, you might just
// return this list, but here we will create a bundle to encode.
List<IResource> resources = new ArrayList<IResource>();
resources.add(org);
resources.add(patient);
// Create a bundle with both
Bundle b = Bundle.withResources(resources, ourCtx, "http://example.com/base");
// Encode the buntdle
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
System.out.println(encoded);]]></source>
<p>
This will give the following output:
</p>
<source><![CDATA[<feed xmlns="http://www.w3.org/2005/Atom">
<entry>
<title>Organization Organization/65546</title>
<id>http://example.com/base/Organization/65546</id>
<published>2014-10-14T09:22:54-04:00</published>
<link rel="self" href="http://example.com/base/Organization/65546"/>
<content type="text/xml">
<Organization xmlns="http://hl7.org/fhir">
<name value="Contained Test Organization"/>
</Organization>
</content>
</entry>
<entry>
<title>Patient Patient/1333</title>
<id>http://example.com/base/Patient/1333</id>
<published>2014-10-14T09:22:54-04:00</published>
<link rel="self" href="http://example.com/base/Patient/1333"/>
<content type="text/xml">
<Patient xmlns="http://hl7.org/fhir">
<identifier>
<system value="urn:mrns"/>
<value value="253345"/>
</identifier>
<managingOrganization>
<reference value="Organization/65546"/>
</managingOrganization>
</Patient>
</content>
</entry>
</feed>]]></source>
</subsection>
<subsection name="Contained Resources">
<p>
On the other hand, if the linked resource
does not have an ID set, the linked resource will
be included in the returned bundle as a "contained" resource. In this
case, HAPI itself will define a local reference ID (e.g. "#001").
case, HAPI itself will define a local reference ID (e.g. "#1").
</p>
<source><![CDATA[Organization org = new Organization();
// org.setId("Organization/65546");
org.getName().setValue("Normal (not contained) Test Organization");
<source><![CDATA[// 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");
patient.getManagingOrganization().setResource(patient);]]></source>
// Put the organization as a reference in the patient resource
patient.getManagingOrganization().setResource(org);
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
System.out.println(encoded);]]></source>
<p>
This will give the following output:
</p>
<source><![CDATA[<Patient xmlns="http://hl7.org/fhir">
<contained>
<Organization xmlns="http://hl7.org/fhir" id="1">
<name value="Contained Test Organization"/>
</Organization>
</contained>
<identifier>
<system value="urn:mrns"/>
<value value="253345"/>
</identifier>
<managingOrganization>
<reference value="#1"/>
</managingOrganization>
</Patient>]]></source>
</subsection>

View File

@ -133,11 +133,11 @@ public class ContainedResourceEncodingTest {
final String expectedCompXml = parser.encodeResourceToString(this.comp);
logger.debug("[xmlEncoding] first encoding: {}", expectedCompXml);
assertEquals(3, this.comp.getContained().getContainedResources().size());
assertEquals(0, this.comp.getContained().getContainedResources().size());
final String actualCompXml = parser.encodeResourceToString(this.comp);
assertEquals(3, this.comp.getContained().getContainedResources().size());
assertEquals(0, this.comp.getContained().getContainedResources().size());
// second encoding - xml could not be parsed back to compositon - i.e.: patient content 4 times! should be the same
// as after first encoding!
@ -145,7 +145,7 @@ public class ContainedResourceEncodingTest {
final String thirdCompXml = parser.encodeResourceToString(this.comp);
assertEquals(3, this.comp.getContained().getContainedResources().size());
assertEquals(0, this.comp.getContained().getContainedResources().size());
// third encoding - xml could not be parsed back to compositon i.e.: patient content 4 times! should be the same as
// afterfirst encoding!

View File

@ -11,6 +11,7 @@ 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 net.sf.json.JSON;
@ -88,6 +89,30 @@ public class JsonParserTest {
}
@Test
public void testEncodeNonContained() {
Organization org = new Organization();
org.setId("Organization/65546");
org.getName().setValue("Contained Test Organization");
Patient patient = new Patient();
patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345");
patient.getManagingOrganization().setResource(org);
Bundle b = Bundle.withResources(Collections.singletonList((IResource)patient), ourCtx, "http://foo");
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeBundleToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("contained")));
assertThat(encoded, containsString("\"reference\":\"Organization/65546\""));
encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, not(containsString("contained")));
assertThat(encoded, containsString("\"reference\":\"Organization/65546\""));
}
@Test
public void testEncodeIds() {
Patient pt =new Patient();
@ -437,6 +462,38 @@ public class JsonParserTest {
}
@Test
public void testEncodeContained() {
// Create an organization
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");
patient.getManagingOrganization().setResource(org);
// 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 buntdle
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeBundleToString(b);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\"", "resourceType\":\"Organization", "id\":\"1\"")));
assertThat(encoded, containsString("reference\":\"#1\""));
encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, stringContainsInOrder(Arrays.asList("\"contained\"", "resourceType\":\"Organization", "id\":\"1\"")));
assertThat(encoded, containsString("reference\":\"#1\""));
}
@Test
public void testEncodeContainedResources() throws IOException {

View File

@ -12,6 +12,7 @@ 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;
@ -78,6 +79,77 @@ public class XmlParserTest {
System.setProperty("file.encoding", "ISO-8859-1");
}
@Test
public void testEncodeNonContained() {
// Create an organization
Organization org = new Organization();
org.setId("Organization/65546");
org.getName().setValue("Contained Test Organization");
// Create a patient
Patient patient = new Patient();
patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345");
patient.getManagingOrganization().setResource(org);
// Create a list containing both resources. In a server method, you might just
// return this list, but here we will create a bundle to encode.
List<IResource> resources = new ArrayList<IResource>();
resources.add(org);
resources.add(patient);
// Create a bundle with both
Bundle b = Bundle.withResources(resources, ourCtx, "http://example.com/base");
// Encode the buntdle
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("<contained>")));
assertThat(encoded, containsString("<reference value=\"Organization/65546\"/>"));
encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, not(containsString("<contained>")));
assertThat(encoded, containsString("<reference value=\"Organization/65546\"/>"));
}
@Test
public void testEncodeContained() {
// 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 = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
assertThat(encoded, containsString("<contained>"));
assertThat(encoded, containsString("<reference value=\"#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 buntdle
encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
ourLog.info(encoded);
assertThat(encoded, containsString("<contained>"));
assertThat(encoded, containsString("<reference value=\"#1\"/>"));
}
/**
* Thanks to Alexander Kley!
*/
@ -85,9 +157,9 @@ public class XmlParserTest {
public void testParseContainedBinaryResource() {
byte[] bin = new byte[] {0,1,2,3,4};
final Binary binary = new Binary("PatientConsent", bin);
binary.setId(UUID.randomUUID().toString());
// binary.setId(UUID.randomUUID().toString());
DocumentManifest manifest = new DocumentManifest();
manifest.setId(UUID.randomUUID().toString());
// manifest.setId(UUID.randomUUID().toString());
manifest.setType(new CodeableConceptDt("mySystem", "PatientDocument"));
manifest.setMasterIdentifier("mySystem", UUID.randomUUID().toString());
manifest.addContent().setResource(binary);

View File

@ -1,7 +1,8 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
@ -25,6 +26,8 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.net.UrlEscapers;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
@ -42,6 +45,7 @@ import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.PortUtil;
/**
@ -121,6 +125,23 @@ public class SearchTest {
assertEquals("bbb", p.getIdentifier().get(1).getValue().getValue());
}
@Test
public void testSearchWithTokenParameter() throws Exception {
String token = UrlEscapers.urlFragmentEscaper().asFunction().apply("http://www.dmix.gov/vista/2957|301");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?tokenParam="+token);
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
assertEquals(1, bundle.getEntries().size());
Patient p = bundle.getResources(Patient.class).get(0);
assertEquals("http://www.dmix.gov/vista/2957", p.getNameFirstRep().getFamilyAsSingleString());
assertEquals("301", p.getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testSearchByPost() throws Exception {
HttpPost filePost = new HttpPost("http://localhost:" + ourPort + "/Patient/_search");
@ -325,6 +346,18 @@ public class SearchTest {
}
@Search()
public List<Patient> findPatientWithToken(@RequiredParam(name = "tokenParam") TokenParam theParam) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
Patient patient = new Patient();
patient.setId("1");
patient.addName().addFamily(theParam.getSystem()).addGiven(theParam.getValue());
retVal.add(patient);
return retVal;
}
@Search(queryName = "findWithLinks")
public List<Patient> findWithLinks() {
ArrayList<Patient> retVal = new ArrayList<Patient>();