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. have also caused resource validation to fail occasionally on these platforms as well.
Thanks to Bill de Beaubien for reporting! Thanks to Bill de Beaubien for reporting!
</action> </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>
<release version="0.6" date="2014-Sep-08" description="This release brings a number of new features and bug fixes!"> <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. * Create and return a new JSON parser.
* *
* <p> * <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 * 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> * </p>
*/ */
@ -261,6 +264,9 @@ public class FhirContext {
* Create and return a new XML parser. * Create and return a new XML parser.
* *
* <p> * <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 * 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> * </p>
*/ */

View File

@ -25,7 +25,9 @@ import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -45,29 +47,60 @@ import ca.uhn.fhir.model.primitive.IdDt;
public abstract class BaseParser implements IParser { public abstract class BaseParser implements IParser {
private boolean mySuppressNarratives; private ContainedResources myContainedResources;
private FhirContext myContext; private FhirContext myContext;
private boolean mySuppressNarratives;
public BaseParser(FhirContext theContext) { public BaseParser(FhirContext theContext) {
myContext = theContext; myContext = theContext;
} }
@Override private void containResourcesForEncoding(ContainedResources theContained, IResource theResource, IResource theTarget) {
public TagList parseTagList(String theString) { List<ResourceReferenceDt> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, ResourceReferenceDt.class);
return parseTagList(new StringReader(theString));
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") for (ResourceReferenceDt next : allElements) {
@Override IResource resource = next.getResource();
public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) { if (resource != null) {
StringReader reader = new StringReader(theMessageString); if (resource.getId().isEmpty()) {
return (T) parseResource(theResourceType, reader); 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 @Override
public Bundle parseBundle(String theXml) throws ConfigurationException, DataFormatException { public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
StringReader reader = new StringReader(theXml); if (theBundle == null) {
return parseBundle(reader); 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 @Override
@ -92,24 +125,15 @@ public abstract class BaseParser implements IParser {
return stringWriter.toString(); return stringWriter.toString();
} }
@Override ContainedResources getContainedResources() {
public String encodeBundleToString(Bundle theBundle) throws DataFormatException { return myContainedResources;
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(); /**
} * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded values.
*/
@Override public boolean getSuppressNarratives() {
public IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException { return mySuppressNarratives;
return parseResource(null, theMessageString);
} }
@Override @Override
@ -117,49 +141,38 @@ public abstract class BaseParser implements IParser {
return parseBundle(null, theReader); 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 @Override
public IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException { public IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException {
return parseResource(null, theReader); return parseResource(null, theReader);
} }
public void containResourcesForEncoding(IResource theResource) { @Override
containResourcesForEncoding(theResource, theResource); public IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
return parseResource(null, theMessageString);
} }
private long myNextContainedId = 1; @Override
public TagList parseTagList(String theString) {
private void containResourcesForEncoding(IResource theResource, IResource theTarget) { return parseTagList(new StringReader(theString));
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 IParser setSuppressNarratives(boolean theSuppressNarratives) {
mySuppressNarratives = theSuppressNarratives;
return this;
} }
protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IElement> type) { 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); throw new DataFormatException(nextChild + " has no child of type " + type);
} }
@Override static class ContainedResources {
public IParser setSuppressNarratives(boolean theSuppressNarratives) { private long myNextContainedId = 1;
mySuppressNarratives = theSuppressNarratives;
return this; 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.IResource;
import ca.uhn.fhir.model.api.TagList; 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 { public interface IParser {
String encodeBundleToString(Bundle theBundle) throws DataFormatException; String encodeBundleToString(Bundle theBundle) throws DataFormatException;
@ -40,26 +46,24 @@ public interface IParser {
void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException, DataFormatException; void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException, DataFormatException;
/** /**
* Encodes a tag list, as defined in the <a * Encodes a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</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 * @return An encoded tag list
*/ */
String encodeTagListToString(TagList theTagList); String encodeTagListToString(TagList theTagList);
/** /**
* Encodes a tag list, as defined in the <a * Encodes a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</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
* @param theWriter The writer to encode to * The tag list to encode. Must not be null.
* @param theWriter
* The writer to encode to
*/ */
void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException; void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException;
<T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader); <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader);
Bundle parseBundle(Reader theReader); Bundle parseBundle(Reader theReader);
@ -70,15 +74,12 @@ public interface IParser {
* Parses a resource * Parses a resource
* *
* @param theResourceType * @param theResourceType
* The resource type to use. This can be used to explicitly * 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)
* specify a class which extends a built-in type (e.g. a custom
* type extending the default Patient class)
* @param theReader * @param theReader
* The reader to parse input from. Note that the Reader will not be closed by the parser upon completion. * The reader to parse input from. Note that the Reader will not be closed by the parser upon completion.
* @return A parsed resource * @return A parsed resource
* @throws DataFormatException * @throws DataFormatException
* If the resource can not be parsed because the data is not * If the resource can not be parsed because the data is not recognized or invalid for any reason
* recognized or invalid for any reason
*/ */
<T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException; <T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
@ -86,15 +87,12 @@ public interface IParser {
* Parses a resource * Parses a resource
* *
* @param theResourceType * @param theResourceType
* The resource type to use. This can be used to explicitly * 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)
* specify a class which extends a built-in type (e.g. a custom
* type extending the default Patient class)
* @param theString * @param theString
* The string to parse * The string to parse
* @return A parsed resource * @return A parsed resource
* @throws DataFormatException * @throws DataFormatException
* If the resource can not be parsed because the data is not * If the resource can not be parsed because the data is not recognized or invalid for any reason
* recognized or invalid for any reason
*/ */
<T extends IResource> T parseResource(Class<T> theResourceType, String theString) throws DataFormatException; <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. * The reader to parse input from. Note that the Reader will not be closed by the parser upon completion.
* @return A parsed resource * @return A parsed resource
* @throws DataFormatException * @throws DataFormatException
* If the resource can not be parsed because the data is not * If the resource can not be parsed because the data is not recognized or invalid for any reason
* recognized or invalid for any reason
*/ */
IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException; IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException;
@ -117,15 +114,12 @@ public interface IParser {
* The string to parse * The string to parse
* @return A parsed resource * @return A parsed resource
* @throws DataFormatException * @throws DataFormatException
* If the resource can not be parsed because the data is not * If the resource can not be parsed because the data is not recognized or invalid for any reason
* recognized or invalid for any reason
*/ */
IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException; IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException;
/** /**
* Parses a tag list, as defined in the <a * Parses a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</a>.
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
* *
* @param theReader * @param theReader
* A reader which will supply a tag list * A reader which will supply a tag list
@ -134,9 +128,7 @@ public interface IParser {
TagList parseTagList(Reader theReader); TagList parseTagList(Reader theReader);
/** /**
* Parses a tag list, as defined in the <a * Parses a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR Specification</a>.
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
* *
* @param theString * @param theString
* A string containing a tag list * A string containing a tag list
@ -145,20 +137,16 @@ public interface IParser {
TagList parseTagList(String theString); TagList parseTagList(String theString);
/** /**
* Sets the "pretty print" flag, meaning that the parser will encode * 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.
* resources with human-readable spacing and newlines between elements
* instead of condensing output as much as possible.
* *
* @param thePrettyPrint * @param thePrettyPrint
* The flag * The flag
* @return Returns an instance of <code>this</code> parser so that method * @return Returns an instance of <code>this</code> parser so that method calls can be conveniently chained
* calls can be conveniently chained
*/ */
IParser setPrettyPrint(boolean thePrettyPrint); IParser setPrettyPrint(boolean thePrettyPrint);
/** /**
* If set to <code>true</code> (default is <code>false</code>), narratives * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded values.
* will not be included in the encoded values.
*/ */
IParser setSuppressNarratives(boolean theSuppressNarratives); 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, 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()) { switch (theChildDef.getChildType()) {
case PRIMITIVE_DATATYPE: { case PRIMITIVE_DATATYPE: {
@ -278,7 +278,7 @@ public class JsonParser extends BaseParser implements IParser {
if (theValue instanceof ExtensionDt) { if (theValue instanceof ExtensionDt) {
theWriter.write("url", ((ExtensionDt) theValue).getUrlAsString()); theWriter.write("url", ((ExtensionDt) theValue).getUrlAsString());
} }
encodeCompositeElementToStreamWriter(theResDef, theResource, theValue, theWriter, childCompositeDef); encodeCompositeElementToStreamWriter(theResDef, theResource, theValue, theWriter, childCompositeDef, theIsSubElementWithinResource);
theWriter.writeEnd(); theWriter.writeEnd();
break; break;
} }
@ -297,6 +297,16 @@ public class JsonParser extends BaseParser implements IParser {
reference = myContext.getResourceDefinition(value.getResourceType()).getName() + '/' + 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();
}
}
}
if (StringUtils.isNotBlank(reference)) { if (StringUtils.isNotBlank(reference)) {
theWriter.write(XmlParser.RESREF_REFERENCE, reference); theWriter.write(XmlParser.RESREF_REFERENCE, reference);
@ -313,6 +323,10 @@ public class JsonParser extends BaseParser implements IParser {
for (IResource next : value.getContainedResources()) { for (IResource next : value.getContainedResources()) {
encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true); 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(); theWriter.writeEnd();
break; break;
} }
@ -341,7 +355,7 @@ public class JsonParser extends BaseParser implements IParser {
} }
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter, 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) { for (BaseRuntimeChildDefinition nextChild : theChildren) {
if (nextChild instanceof RuntimeChildNarrativeDefinition) { if (nextChild instanceof RuntimeChildNarrativeDefinition) {
INarrativeGenerator gen = myContext.getNarrativeGenerator(); INarrativeGenerator gen = myContext.getNarrativeGenerator();
@ -351,7 +365,7 @@ public class JsonParser extends BaseParser implements IParser {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype()); String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theIsSubElementWithinResource);
continue; continue;
} }
} }
@ -371,8 +385,14 @@ public class JsonParser extends BaseParser implements IParser {
int valueIdx = 0; int valueIdx = 0;
for (IElement nextValue : values) { for (IElement nextValue : values) {
if (nextValue == null || nextValue.isEmpty()) { if (nextValue == null || nextValue.isEmpty()) {
if (nextValue instanceof ContainedDt) {
if (theIsSubElementWithinResource || getContainedResources().isEmpty()) {
continue; continue;
} }
} else {
continue;
}
}
Class<? extends IElement> type = nextValue.getClass(); Class<? extends IElement> type = nextValue.getClass();
String childName = nextChild.getChildNameByDatatype(type); String childName = nextChild.getChildNameByDatatype(type);
@ -382,6 +402,10 @@ public class JsonParser extends BaseParser implements IParser {
} }
boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE; boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
if (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES && theIsSubElementWithinResource) {
continue;
}
if (nextChild instanceof RuntimeChildDeclaredExtensionDefinition) { if (nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
// Don't encode extensions // Don't encode extensions
// RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild; // RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild;
@ -399,13 +423,13 @@ public class JsonParser extends BaseParser implements IParser {
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) { if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
theEventWriter.writeStartArray(childName); theEventWriter.writeStartArray(childName);
inArray = true; inArray = true;
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theIsSubElementWithinResource);
} else { } else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theIsSubElementWithinResource);
} }
currentChildName = childName; currentChildName = childName;
} else { } else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theIsSubElementWithinResource);
} }
if (nextValue instanceof ISupportsUndeclaredExtensions && primitive) { 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, 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); extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, resDef, theResDef, theResource);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions()); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIsSubElementWithinResource);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren()); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren(),theIsSubElementWithinResource);
} }
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull,
boolean theIsSubElementWithinResource) throws IOException { 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) { if (!theIsSubElementWithinResource) {
super.containResourcesForEncoding(theResource); super.containResourcesForEncoding(theResource);
} }
@ -474,8 +508,8 @@ public class JsonParser extends BaseParser implements IParser {
} }
theEventWriter.write("resourceType", resDef.getName()); theEventWriter.write("resourceType", resDef.getName());
if (theIsSubElementWithinResource && theResource.getId() != null && isNotBlank(theResource.getId().getValue())) { if (theResourceId != null) {
theEventWriter.write("id", theResource.getId().getValue()); theEventWriter.write("id", theResourceId);
} }
if (theResource instanceof Binary) { if (theResource instanceof Binary) {
@ -483,7 +517,7 @@ public class JsonParser extends BaseParser implements IParser {
theEventWriter.write("contentType", bin.getContentType()); theEventWriter.write("contentType", bin.getContentType());
theEventWriter.write("content", bin.getContentAsBase64()); theEventWriter.write("content", bin.getContentAsBase64());
} else { } else {
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, resDef); encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, resDef, theIsSubElementWithinResource);
} }
theEventWriter.writeEnd(); theEventWriter.writeEnd();
} }
@ -972,7 +1006,7 @@ public class JsonParser extends BaseParser implements IParser {
// theEventWriter, myValue, def, "value" + // theEventWriter, myValue, def, "value" +
// WordUtils.capitalize(def.getName())); // WordUtils.capitalize(def.getName()));
String childName = myDef.getChildNameByDatatype(myValue.getClass()); 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); // 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()); throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName());
} }
BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(value.getClass()); 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); // theEventWriter.name(myUndeclaredExtension.get);

View File

@ -34,8 +34,6 @@ import java.util.List;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter; 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.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
@ -153,9 +152,9 @@ public class XmlParser extends BaseParser implements IParser {
eventWriter.writeNamespace("at", TOMBSTONES_NS); eventWriter.writeNamespace("at", TOMBSTONES_NS);
eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString()); eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString());
eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString()); eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString());
if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty()==false) { if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty() == false) {
eventWriter.writeStartElement(TOMBSTONES_NS, "by"); eventWriter.writeStartElement(TOMBSTONES_NS, "by");
if (nextEntry.getDeletedByName().isEmpty()==false) { if (nextEntry.getDeletedByName().isEmpty() == false) {
eventWriter.writeStartElement(TOMBSTONES_NS, "name"); eventWriter.writeStartElement(TOMBSTONES_NS, "name");
eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue()); eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue());
eventWriter.writeEndElement(); eventWriter.writeEndElement();
@ -167,7 +166,7 @@ public class XmlParser extends BaseParser implements IParser {
} }
eventWriter.writeEndElement(); eventWriter.writeEndElement();
} }
if (nextEntry.getDeletedComment().isEmpty()==false) { if (nextEntry.getDeletedComment().isEmpty() == false) {
eventWriter.writeStartElement(TOMBSTONES_NS, "comment"); eventWriter.writeStartElement(TOMBSTONES_NS, "comment");
eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue()); eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue());
eventWriter.writeEndElement(); eventWriter.writeEndElement();
@ -214,7 +213,6 @@ public class XmlParser extends BaseParser implements IParser {
eventWriter.writeEndElement(); eventWriter.writeEndElement();
} }
eventWriter.writeEndElement(); // entry eventWriter.writeEndElement(); // entry
} }
@ -336,15 +334,15 @@ public class XmlParser extends BaseParser implements IParser {
throw new DataFormatException(e1); throw new DataFormatException(e1);
} }
// XMLEventReader streamReader; // XMLEventReader streamReader;
// try { // try {
// streamReader = myXmlInputFactory.createXMLEventReader(theReader); // streamReader = myXmlInputFactory.createXMLEventReader(theReader);
// } catch (XMLStreamException e) { // } catch (XMLStreamException e) {
// throw new DataFormatException(e); // throw new DataFormatException(e);
// } catch (FactoryConfigurationError e) { // } catch (FactoryConfigurationError e) {
// throw new ConfigurationException("Failed to initialize STaX event factory", e); // throw new ConfigurationException("Failed to initialize STaX event factory", e);
// } // }
// return streamReader; // return streamReader;
} }
private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) { private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
@ -426,8 +424,12 @@ public class XmlParser extends BaseParser implements IParser {
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName, private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName,
BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException { BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
if (nextValue.isEmpty()) { if (nextValue.isEmpty()) {
if (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES && getContainedResources().isEmpty()==false && theIncludedResource == false) {
// We still want to go in..
} else {
return; return;
} }
}
switch (childDef.getChildType()) { switch (childDef.getChildType()) {
case PRIMITIVE_DATATYPE: { case PRIMITIVE_DATATYPE: {
@ -467,6 +469,10 @@ public class XmlParser extends BaseParser implements IParser {
for (IResource next : value.getContainedResources()) { for (IResource next : value.getContainedResources()) {
encodeResourceToXmlStreamWriter(next, theEventWriter, true); encodeResourceToXmlStreamWriter(next, theEventWriter, true);
} }
for (IResource next : getContainedResources().getContainedResources()) {
IdDt resourceId = getContainedResources().getResourceId(next);
encodeResourceToXmlStreamWriter(next, theEventWriter, true, resourceId.getValue());
}
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
break; break;
} }
@ -512,7 +518,7 @@ public class XmlParser extends BaseParser implements IParser {
} }
for (IElement nextValue : values) { for (IElement nextValue : values) {
if (nextValue == null || nextValue.isEmpty()) { if ((nextValue == null || nextValue.isEmpty()) && !(nextValue instanceof ContainedDt)) {
continue; continue;
} }
Class<? extends IElement> type = nextValue.getClass(); 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)) { if (StringUtils.isNotBlank(reference)) {
theEventWriter.writeStartElement(RESREF_REFERENCE); theEventWriter.writeStartElement(RESREF_REFERENCE);
theEventWriter.writeAttribute("value", 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 { 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) { if (!theIncludedResource) {
super.containResourcesForEncoding(theResource); super.containResourcesForEncoding(theResource);
} }
@ -600,8 +621,8 @@ public class XmlParser extends BaseParser implements IParser {
theEventWriter.writeStartElement(resDef.getName()); theEventWriter.writeStartElement(resDef.getName());
theEventWriter.writeDefaultNamespace(FHIR_NS); theEventWriter.writeDefaultNamespace(FHIR_NS);
if (theIncludedResource && StringUtils.isNotBlank(theResource.getId().getValue())) { if (theResourceId != null) {
theEventWriter.writeAttribute("id", theResource.getId().getValue()); theEventWriter.writeAttribute("id", theResourceId);
} }
if (theResource instanceof Binary) { if (theResource instanceof Binary) {

View File

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

View File

@ -72,13 +72,15 @@ public class UnprocessableEntityException extends BaseServerResponseException {
} }
private static OperationOutcome toOperationOutcome(String... theMessage) { private static OperationOutcome toOperationOutcome(String... theMessage) {
OperationOutcome operationOutcome = new OperationOutcome(); OperationOutcome OperationOutcome = new OperationOutcome();
if (theMessage != null) { if (theMessage != null) {
for (String next : theMessage) { 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> <p>
In the following example, the Organization resource has an ID set, so it will not 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 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> </p>
<source><![CDATA[Organization org = new Organization(); <source><![CDATA[// Create an organization
Organization org = new Organization();
org.setId("Organization/65546"); org.setId("Organization/65546");
org.getName().setValue("Contained Test Organization"); org.getName().setValue("Contained Test Organization");
// Create a patient
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("Patient/1333"); patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345"); 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> <p>
On the other hand, if the linked resource On the other hand, if the linked resource
does not have an ID set, the linked resource will does not have an ID set, the linked resource will
be included in the returned bundle as a "contained" resource. In this 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> </p>
<source><![CDATA[Organization org = new Organization(); <source><![CDATA[// Create an organization, note that the organization does not have an ID
// org.setId("Organization/65546"); Organization org = new Organization();
org.getName().setValue("Normal (not contained) Test Organization"); org.getName().setValue("Contained Test Organization");
// Create a patient
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("Patient/1333"); patient.setId("Patient/1333");
patient.addIdentifier("urn:mrns", "253345"); 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> </subsection>

View File

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

View File

@ -11,6 +11,7 @@ import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import net.sf.json.JSON; 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 @Test
public void testEncodeIds() { public void testEncodeIds() {
Patient pt =new Patient(); 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 @Test
public void testEncodeContainedResources() throws IOException { public void testEncodeContainedResources() throws IOException {

View File

@ -12,6 +12,7 @@ import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -78,6 +79,77 @@ public class XmlParserTest {
System.setProperty("file.encoding", "ISO-8859-1"); 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! * Thanks to Alexander Kley!
*/ */
@ -85,9 +157,9 @@ public class XmlParserTest {
public void testParseContainedBinaryResource() { public void testParseContainedBinaryResource() {
byte[] bin = new byte[] {0,1,2,3,4}; byte[] bin = new byte[] {0,1,2,3,4};
final Binary binary = new Binary("PatientConsent", bin); final Binary binary = new Binary("PatientConsent", bin);
binary.setId(UUID.randomUUID().toString()); // binary.setId(UUID.randomUUID().toString());
DocumentManifest manifest = new DocumentManifest(); DocumentManifest manifest = new DocumentManifest();
manifest.setId(UUID.randomUUID().toString()); // manifest.setId(UUID.randomUUID().toString());
manifest.setType(new CodeableConceptDt("mySystem", "PatientDocument")); manifest.setType(new CodeableConceptDt("mySystem", "PatientDocument"));
manifest.setMasterIdentifier("mySystem", UUID.randomUUID().toString()); manifest.setMasterIdentifier("mySystem", UUID.randomUUID().toString());
manifest.addContent().setResource(binary); manifest.addContent().setResource(binary);

View File

@ -1,7 +1,8 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -25,6 +26,8 @@ import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import com.google.common.net.UrlEscapers;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; 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.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
/** /**
@ -121,6 +125,23 @@ public class SearchTest {
assertEquals("bbb", p.getIdentifier().get(1).getValue().getValue()); 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 @Test
public void testSearchByPost() throws Exception { public void testSearchByPost() throws Exception {
HttpPost filePost = new HttpPost("http://localhost:" + ourPort + "/Patient/_search"); 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") @Search(queryName = "findWithLinks")
public List<Patient> findWithLinks() { public List<Patient> findWithLinks() {
ArrayList<Patient> retVal = new ArrayList<Patient>(); ArrayList<Patient> retVal = new ArrayList<Patient>();