diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml index 687e6ff7149..c76ed88a524 100644 --- a/hapi-fhir-base/src/changes/changes.xml +++ b/hapi-fhir-base/src/changes/changes.xml @@ -7,7 +7,7 @@ - + Make it easier to add HTTP Basic Authorization headers to RESTful client requests and an example]]> illustrating how it works @@ -15,6 +15,10 @@ Correct a dependency on commons-io that was causing issues with the Tinder build + + Fix an issue where extensions with a resource reference as the value show up incorrectly + (wrong element name for undeclared extensions, and empty content for declared extensions) + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java index 4226477a83f..b98cc3331f2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java @@ -23,12 +23,15 @@ package ca.uhn.fhir.context; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import ca.uhn.fhir.model.api.IElement; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Extension; @@ -133,7 +136,15 @@ public class RuntimeChildDeclaredExtensionDefinition extends BaseRuntimeDeclared BaseRuntimeElementDefinition elementDef = theClassToElementDefinitions.get(myChildType); if (elementDef instanceof RuntimePrimitiveDatatypeDefinition || elementDef instanceof RuntimeCompositeDatatypeDefinition) { myDatatypeChildName = "value" + elementDef.getName().substring(0, 1).toUpperCase() + elementDef.getName().substring(1); - myChildDef = elementDef; + if ("valueResourceReference".equals(myDatatypeChildName)) { + // Per one of the examples here: http://hl7.org/implement/standards/fhir/extensibility.html#extension + myDatatypeChildName = "valueResource"; + List> types = new ArrayList>(); + types.add(IResource.class); + myChildDef = new RuntimeResourceReferenceDefinition("valueResource", types); + }else { + myChildDef = elementDef; + } } else { RuntimeResourceBlockDefinition extDef = ((RuntimeResourceBlockDefinition) elementDef); for (RuntimeChildDeclaredExtensionDefinition next : extDef.getExtensions()) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java index e45756ebbf0..9d76a9958f3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java @@ -94,7 +94,7 @@ public class RuntimeChildUndeclaredExtensionDefinition extends BaseRuntimeChildD } // Resource Reference - myDatatypeToAttributeName.put(ResourceReferenceDt.class, "valueReference"); + myDatatypeToAttributeName.put(ResourceReferenceDt.class, "valueResource"); List> types = new ArrayList>(); types.add(IResource.class); RuntimeResourceReferenceDefinition def = new RuntimeResourceReferenceDefinition("valueResource", types); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java index 49b77601619..47d96aad4fc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java @@ -26,7 +26,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) -@Target(value= {ElementType.FIELD, ElementType.TYPE}) +@Target(value= {ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER}) public @interface Description { /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 1135126f994..b84be27baf1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -59,6 +59,11 @@ public abstract class BaseParser implements IParser { return parseResource(null, theMessageString); } + @Override + public Bundle parseBundle(Reader theReader) { + return parseBundle(null, theReader); + } + @Override public IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException { return parseResource(null, theReader); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java index f0c06b17450..90c916b11d8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java @@ -38,17 +38,19 @@ public interface IParser { void encodeResourceToWriter(IResource theResource, Writer stringWriter) throws IOException, DataFormatException; + Bundle parseBundle(Class theResourceType, Reader theReader); + Bundle parseBundle(Reader theReader); Bundle parseBundle(String theMessageString) throws ConfigurationException, DataFormatException; - IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException; - - IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException; + T parseResource(Class theResourceType, Reader theReader); T parseResource(Class theResourceType, String theMessageString); - T parseResource(Class theResourceType, Reader theReader); + IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException; + + IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException; IParser setPrettyPrint(boolean thePrettyPrint); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 025c97f5c8f..fb44d3f7e56 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -56,9 +56,11 @@ 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; import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition; +import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.BaseBundle; import ca.uhn.fhir.model.api.Bundle; @@ -83,13 +85,66 @@ import ca.uhn.fhir.narrative.INarrativeGenerator; public class JsonParser extends BaseParser implements IParser { + private static final Set BUNDLE_TEXTNODE_CHILDREN; + static { + HashSet hashSet = new HashSet(); + hashSet.add("title"); + hashSet.add("id"); + hashSet.add("updated"); + hashSet.add("published"); + BUNDLE_TEXTNODE_CHILDREN = Collections.unmodifiableSet(hashSet); + } + private FhirContext myContext; + private boolean myPrettyPrint; public JsonParser(FhirContext theContext) { myContext = theContext; } + private void addToHeldExtensions(int valueIdx, ArrayList> list, RuntimeChildDeclaredExtensionDefinition theDef, IElement theValue) { + list.ensureCapacity(valueIdx); + while (list.size() <= valueIdx) { + list.add(null); + } + if (list.get(valueIdx) == null) { + list.set(valueIdx, new ArrayList()); + } + list.get(valueIdx).add(new HeldExtension(theDef, theValue)); + } + + private void addToHeldExtensions(int valueIdx, List ext, ArrayList> list) { + if (ext.size() > 0) { + list.ensureCapacity(valueIdx); + while (list.size() <= valueIdx) { + list.add(null); + } + if (list.get(valueIdx) == null) { + list.set(valueIdx, new ArrayList()); + } + for (ExtensionDt next : ext) { + list.get(valueIdx).add(new HeldExtension(next)); + } + } + } + + private void assertObjectOfType(JsonValue theResourceTypeObj, ValueType theValueType, String thePosition) { + if (theResourceTypeObj.getValueType() != theValueType) { + throw new DataFormatException("Invalid content of element " + thePosition + ", expected " + theValueType); + } + } + + private JsonGenerator createJsonGenerator(Writer theWriter) { + Map properties = new HashMap(1); + if (myPrettyPrint) { + properties.put(JsonGenerator.PRETTY_PRINTING, myPrettyPrint); + } + JsonGeneratorFactory jgf = Json.createGeneratorFactory(properties); + JsonGenerator eventWriter = jgf.createGenerator(theWriter); + return eventWriter; + } + @Override public String encodeBundleToString(Bundle theBundle) throws DataFormatException, IOException { if (theBundle == null) { @@ -154,324 +209,6 @@ public class JsonParser extends BaseParser implements IParser { eventWriter.close(); } - @Override - public String encodeResourceToString(IResource theResource) throws DataFormatException, IOException { - Writer stringWriter = new StringWriter(); - encodeResourceToWriter(theResource, stringWriter); - return stringWriter.toString(); - } - - @Override - public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException { - Validate.notNull(theResource, "Resource can not be null"); - - JsonGenerator eventWriter = createJsonGenerator(theWriter); - - RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); - encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null); - eventWriter.flush(); - } - - @Override - public Bundle parseBundle(Reader theReader) { - JsonReader reader = Json.createReader(theReader); - JsonObject object = reader.readObject(); - - JsonValue resourceTypeObj = object.get("resourceType"); - assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType"); - String resourceType = ((JsonString) resourceTypeObj).getString(); - if (!"Bundle".equals(resourceType)) { - throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'"); - } - - ParserState state = ParserState.getPreAtomInstance(myContext, true); - state.enteringNewElement(null, "feed"); - - parseBundleChildren(object, state); - - state.endingElement(); - - Bundle retVal = state.getObject(); - - return retVal; - } - - @Override - public T parseResource(Class theResourceType, String theMessageString) { - return parseResource(theResourceType, new StringReader(theMessageString)); - } - - @Override - public T parseResource(Class theResourceType, Reader theReader) { - JsonReader reader = Json.createReader(theReader); - JsonObject object = reader.readObject(); - - JsonValue resourceTypeObj = object.get("resourceType"); - assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType"); - String resourceType = ((JsonString) resourceTypeObj).getString(); - - RuntimeResourceDefinition def; - if (theResourceType != null) { - def = myContext.getResourceDefinition(theResourceType); - } else { - def = myContext.getResourceDefinition(resourceType); - } - - ParserState state = ParserState.getPreResourceInstance(def.getImplementingClass(), myContext, true); - state.enteringNewElement(null, def.getName()); - - parseChildren(object, state); - - state.endingElement(); - - @SuppressWarnings("unchecked") - T retVal = (T) state.getObject(); - - return retVal; - } - - private void parseChildren(JsonObject theObject, ParserState theState) { - String elementId = null; - for (String nextName : theObject.keySet()) { - if ("resourceType".equals(nextName)) { - continue; - } else if ("id".equals(nextName)) { - elementId = theObject.getString(nextName); - continue; - } else if ("extension".equals(nextName)) { - JsonArray array = theObject.getJsonArray(nextName); - parseExtension(theState, array, false); - continue; - } else if ("modifierExtension".equals(nextName)) { - JsonArray array = theObject.getJsonArray(nextName); - parseExtension(theState, array, true); - continue; - } else if (nextName.charAt(0) == '_') { - continue; - } - - JsonValue nextVal = theObject.get(nextName); - JsonValue alternateVal = theObject.get('_' + nextName); - - parseChildren(theState, nextName, nextVal, alternateVal); - - } - - if (elementId != null) { - IElement object = theState.getObject(); - if (object instanceof IIdentifiableElement) { - ((IIdentifiableElement) object).setId(new IdDt(elementId)); - } - } - } - - private static final Set BUNDLE_TEXTNODE_CHILDREN; - - static { - HashSet hashSet = new HashSet(); - hashSet.add("title"); - hashSet.add("id"); - hashSet.add("updated"); - hashSet.add("published"); - BUNDLE_TEXTNODE_CHILDREN = Collections.unmodifiableSet(hashSet); - } - - private void parseBundleChildren(JsonObject theObject, ParserState theState) { - for (String nextName : theObject.keySet()) { - if ("resourceType".equals(nextName)) { - continue; - } else if ("link".equals(nextName)) { - JsonArray entries = theObject.getJsonArray(nextName); - for (JsonValue jsonValue : entries) { - theState.enteringNewElement(null, "link"); - JsonObject linkObj = (JsonObject) jsonValue; - String rel = linkObj.getString("rel", null); - String href = linkObj.getString("href", null); - theState.attributeValue("rel", rel); - theState.attributeValue("href", href); - theState.endingElement(); - } - continue; - } else if ("entry".equals(nextName)) { - JsonArray entries = theObject.getJsonArray(nextName); - for (JsonValue jsonValue : entries) { - theState.enteringNewElement(null, "entry"); - parseBundleChildren((JsonObject) jsonValue, theState); - theState.endingElement(); - } - continue; - } else if (BUNDLE_TEXTNODE_CHILDREN.contains(nextName)) { - theState.enteringNewElement(null, nextName); - theState.string(theObject.getString(nextName, null)); - theState.endingElement(); - continue; - } - - JsonValue nextVal = theObject.get(nextName); - parseChildren(theState, nextName, nextVal, null); - - } - } - - private void parseChildren(ParserState theState, String theName, JsonValue theJsonVal, JsonValue theAlternateVal) { - switch (theJsonVal.getValueType()) { - case ARRAY: { - JsonArray nextArray = (JsonArray) theJsonVal; - JsonArray nextAlternateArray = (JsonArray) theAlternateVal; - for (int i = 0; i < nextArray.size(); i++) { - JsonValue nextObject = nextArray.get(i); - JsonValue nextAlternate = null; - if (nextAlternateArray != null) { - nextAlternate = nextAlternateArray.get(i); - } - parseChildren(theState, theName, nextObject, nextAlternate); - } - break; - } - case OBJECT: { - theState.enteringNewElement(null, theName); - parseAlternates(theAlternateVal, theState); - JsonObject nextObject = (JsonObject) theJsonVal; - boolean preResource = false; - if (theState.isPreResource()) { - String resType = nextObject.getString("resourceType"); - if (isBlank(resType)) { - throw new DataFormatException("Missing 'resourceType' from resource"); - } - theState.enteringNewElement(null, resType); - preResource = true; - } - parseChildren(nextObject, theState); - if (preResource) { - theState.endingElement(); - } - theState.endingElement(); - break; - } - case STRING: { - JsonString nextValStr = (JsonString) theJsonVal; - theState.enteringNewElement(null, theName); - theState.attributeValue("value", nextValStr.getString()); - parseAlternates(theAlternateVal, theState); - theState.endingElement(); - break; - } - case NUMBER: - case FALSE: - case TRUE: - theState.enteringNewElement(null, theName); - theState.attributeValue("value", theJsonVal.toString()); - parseAlternates(theAlternateVal, theState); - theState.endingElement(); - break; - case NULL: - break; - } - } - - private void parseAlternates(JsonValue theAlternateVal, ParserState theState) { - if (theAlternateVal == null || theAlternateVal.getValueType() == ValueType.NULL) { - return; - } - JsonObject alternate = (JsonObject) theAlternateVal; - for (Entry nextEntry : alternate.entrySet()) { - String nextKey = nextEntry.getKey(); - JsonValue nextVal = nextEntry.getValue(); - if ("extension".equals(nextKey)) { - boolean isModifier = false; - JsonArray array = (JsonArray) nextEntry.getValue(); - parseExtension(theState, array, isModifier); - } else if ("modifierExtension".equals(nextKey)) { - boolean isModifier = true; - JsonArray array = (JsonArray) nextEntry.getValue(); - parseExtension(theState, array, isModifier); - } else if ("id".equals(nextKey)) { - switch (nextVal.getValueType()) { - case STRING: - theState.attributeValue("id", ((JsonString) nextVal).getString()); - break; - case NULL: - break; - default: - break; - } - } - } - } - - private void parseExtension(ParserState theState, JsonArray array, boolean theIsModifier) { - // TODO: use theIsModifier - for (int i = 0; i < array.size(); i++) { - JsonObject nextExtObj = array.getJsonObject(i); - String url = nextExtObj.getString("url"); - theState.enteringNewElementExtension(null, url, theIsModifier); - for (Iterator iter = nextExtObj.keySet().iterator(); iter.hasNext();) { - String next = iter.next(); - if ("url".equals(next)) { - continue; - } else if ("extension".equals(next)) { - JsonArray jsonVal = (JsonArray) nextExtObj.get(next); - parseExtension(theState, jsonVal, false); - } else if ("modifierExtension".equals(next)) { - JsonArray jsonVal = (JsonArray) nextExtObj.get(next); - parseExtension(theState, jsonVal, true); - } else { - JsonValue jsonVal = nextExtObj.get(next); - parseChildren(theState, next, jsonVal, null); - } - } - theState.endingElement(); - } - } - - @Override - public IParser setPrettyPrint(boolean thePrettyPrint) { - myPrettyPrint = thePrettyPrint; - return this; - } - - private void addToHeldExtensions(int valueIdx, List ext, ArrayList> list) { - if (ext.size() > 0) { - list.ensureCapacity(valueIdx); - while (list.size() <= valueIdx) { - list.add(null); - } - if (list.get(valueIdx) == null) { - list.set(valueIdx, new ArrayList()); - } - for (ExtensionDt next : ext) { - list.get(valueIdx).add(new HeldExtension(next)); - } - } - } - - private void addToHeldExtensions(int valueIdx, ArrayList> list, RuntimeChildDeclaredExtensionDefinition theDef, IElement theValue) { - list.ensureCapacity(valueIdx); - while (list.size() <= valueIdx) { - list.add(null); - } - if (list.get(valueIdx) == null) { - list.set(valueIdx, new ArrayList()); - } - list.get(valueIdx).add(new HeldExtension(theDef, theValue)); - } - - private void assertObjectOfType(JsonValue theResourceTypeObj, ValueType theValueType, String thePosition) { - if (theResourceTypeObj.getValueType() != theValueType) { - throw new DataFormatException("Invalid content of element " + thePosition + ", expected " + theValueType); - } - } - - private JsonGenerator createJsonGenerator(Writer theWriter) { - Map properties = new HashMap(1); - if (myPrettyPrint) { - properties.put(JsonGenerator.PRETTY_PRINTING, myPrettyPrint); - } - JsonGeneratorFactory jgf = Json.createGeneratorFactory(properties); - JsonGenerator eventWriter = jgf.createGenerator(theWriter); - return eventWriter; - } - private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition theChildDef, String theChildName) throws IOException { @@ -736,6 +473,24 @@ public class JsonParser extends BaseParser implements IParser { theEventWriter.writeEnd(); } + @Override + public String encodeResourceToString(IResource theResource) throws DataFormatException, IOException { + Writer stringWriter = new StringWriter(); + encodeResourceToWriter(theResource, stringWriter); + return stringWriter.toString(); + } + + @Override + public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException { + Validate.notNull(theResource, "Resource can not be null"); + + JsonGenerator eventWriter = createJsonGenerator(theWriter); + + RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); + encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null); + eventWriter.flush(); + } + /** * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object called _name): resource extensions, and extension extensions */ @@ -754,21 +509,20 @@ public class JsonParser extends BaseParser implements IParser { writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions); } - private void writeExtensionsAsDirectChild(IResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List extensions, - List modifierExtensions) throws IOException { - if (extensions.isEmpty() == false) { - theEventWriter.writeStartArray("extension"); - for (HeldExtension next : extensions) { - next.write(resDef, theResource, theEventWriter); + private void extractDeclaredExtensions(IElement theResource, BaseRuntimeElementDefinition resDef, List extensions, List modifierExtensions) { + for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) { + for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) { + if (nextValue != null) { + extensions.add(new HeldExtension(nextDef, nextValue)); + } } - theEventWriter.writeEnd(); } - if (modifierExtensions.isEmpty() == false) { - theEventWriter.writeStartArray("modifierExtension"); - for (HeldExtension next : modifierExtensions) { - next.write(resDef, theResource, theEventWriter); + for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) { + for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) { + if (nextValue != null) { + modifierExtensions.add(new HeldExtension(nextDef, nextValue)); + } } - theEventWriter.writeEnd(); } } @@ -786,23 +540,254 @@ public class JsonParser extends BaseParser implements IParser { } } - private void extractDeclaredExtensions(IElement theResource, BaseRuntimeElementDefinition resDef, List extensions, List modifierExtensions) { - for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) { - for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) { - if (nextValue != null) { - extensions.add(new HeldExtension(nextDef, nextValue)); + private void parseAlternates(JsonValue theAlternateVal, ParserState theState) { + if (theAlternateVal == null || theAlternateVal.getValueType() == ValueType.NULL) { + return; + } + JsonObject alternate = (JsonObject) theAlternateVal; + for (Entry nextEntry : alternate.entrySet()) { + String nextKey = nextEntry.getKey(); + JsonValue nextVal = nextEntry.getValue(); + if ("extension".equals(nextKey)) { + boolean isModifier = false; + JsonArray array = (JsonArray) nextEntry.getValue(); + parseExtension(theState, array, isModifier); + } else if ("modifierExtension".equals(nextKey)) { + boolean isModifier = true; + JsonArray array = (JsonArray) nextEntry.getValue(); + parseExtension(theState, array, isModifier); + } else if ("id".equals(nextKey)) { + switch (nextVal.getValueType()) { + case STRING: + theState.attributeValue("id", ((JsonString) nextVal).getString()); + break; + case NULL: + break; + default: + break; } } } - for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) { - for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) { - if (nextValue != null) { - modifierExtensions.add(new HeldExtension(nextDef, nextValue)); + } + + @Override + public Bundle parseBundle(Class theResourceType, Reader theReader) { + JsonReader reader = Json.createReader(theReader); + JsonObject object = reader.readObject(); + + JsonValue resourceTypeObj = object.get("resourceType"); + assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType"); + String resourceType = ((JsonString) resourceTypeObj).getString(); + if (!"Bundle".equals(resourceType)) { + throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'"); + } + + ParserState state = ParserState.getPreAtomInstance(myContext, theResourceType, true); + state.enteringNewElement(null, "feed"); + + parseBundleChildren(object, state); + + state.endingElement(); + + Bundle retVal = state.getObject(); + + return retVal; + } + + + private void parseBundleChildren(JsonObject theObject, ParserState theState) { + for (String nextName : theObject.keySet()) { + if ("resourceType".equals(nextName)) { + continue; + } else if ("link".equals(nextName)) { + JsonArray entries = theObject.getJsonArray(nextName); + for (JsonValue jsonValue : entries) { + theState.enteringNewElement(null, "link"); + JsonObject linkObj = (JsonObject) jsonValue; + String rel = linkObj.getString("rel", null); + String href = linkObj.getString("href", null); + theState.attributeValue("rel", rel); + theState.attributeValue("href", href); + theState.endingElement(); } + continue; + } else if ("entry".equals(nextName)) { + JsonArray entries = theObject.getJsonArray(nextName); + for (JsonValue jsonValue : entries) { + theState.enteringNewElement(null, "entry"); + parseBundleChildren((JsonObject) jsonValue, theState); + theState.endingElement(); + } + continue; + } else if (BUNDLE_TEXTNODE_CHILDREN.contains(nextName)) { + theState.enteringNewElement(null, nextName); + theState.string(theObject.getString(nextName, null)); + theState.endingElement(); + continue; + } + + JsonValue nextVal = theObject.get(nextName); + parseChildren(theState, nextName, nextVal, null); + + } + } + + private void parseChildren(JsonObject theObject, ParserState theState) { + String elementId = null; + for (String nextName : theObject.keySet()) { + if ("resourceType".equals(nextName)) { + continue; + } else if ("id".equals(nextName)) { + elementId = theObject.getString(nextName); + continue; + } else if ("extension".equals(nextName)) { + JsonArray array = theObject.getJsonArray(nextName); + parseExtension(theState, array, false); + continue; + } else if ("modifierExtension".equals(nextName)) { + JsonArray array = theObject.getJsonArray(nextName); + parseExtension(theState, array, true); + continue; + } else if (nextName.charAt(0) == '_') { + continue; + } + + JsonValue nextVal = theObject.get(nextName); + JsonValue alternateVal = theObject.get('_' + nextName); + + parseChildren(theState, nextName, nextVal, alternateVal); + + } + + if (elementId != null) { + IElement object = theState.getObject(); + if (object instanceof IIdentifiableElement) { + ((IIdentifiableElement) object).setId(new IdDt(elementId)); } } } + private void parseChildren(ParserState theState, String theName, JsonValue theJsonVal, JsonValue theAlternateVal) { + switch (theJsonVal.getValueType()) { + case ARRAY: { + JsonArray nextArray = (JsonArray) theJsonVal; + JsonArray nextAlternateArray = (JsonArray) theAlternateVal; + for (int i = 0; i < nextArray.size(); i++) { + JsonValue nextObject = nextArray.get(i); + JsonValue nextAlternate = null; + if (nextAlternateArray != null) { + nextAlternate = nextAlternateArray.get(i); + } + parseChildren(theState, theName, nextObject, nextAlternate); + } + break; + } + case OBJECT: { + theState.enteringNewElement(null, theName); + parseAlternates(theAlternateVal, theState); + JsonObject nextObject = (JsonObject) theJsonVal; + boolean preResource = false; + if (theState.isPreResource()) { + String resType = nextObject.getString("resourceType"); + if (isBlank(resType)) { + throw new DataFormatException("Missing 'resourceType' from resource"); + } + theState.enteringNewElement(null, resType); + preResource = true; + } + parseChildren(nextObject, theState); + if (preResource) { + theState.endingElement(); + } + theState.endingElement(); + break; + } + case STRING: { + JsonString nextValStr = (JsonString) theJsonVal; + theState.enteringNewElement(null, theName); + theState.attributeValue("value", nextValStr.getString()); + parseAlternates(theAlternateVal, theState); + theState.endingElement(); + break; + } + case NUMBER: + case FALSE: + case TRUE: + theState.enteringNewElement(null, theName); + theState.attributeValue("value", theJsonVal.toString()); + parseAlternates(theAlternateVal, theState); + theState.endingElement(); + break; + case NULL: + break; + } + } + + private void parseExtension(ParserState theState, JsonArray array, boolean theIsModifier) { + // TODO: use theIsModifier + for (int i = 0; i < array.size(); i++) { + JsonObject nextExtObj = array.getJsonObject(i); + String url = nextExtObj.getString("url"); + theState.enteringNewElementExtension(null, url, theIsModifier); + for (Iterator iter = nextExtObj.keySet().iterator(); iter.hasNext();) { + String next = iter.next(); + if ("url".equals(next)) { + continue; + } else if ("extension".equals(next)) { + JsonArray jsonVal = (JsonArray) nextExtObj.get(next); + parseExtension(theState, jsonVal, false); + } else if ("modifierExtension".equals(next)) { + JsonArray jsonVal = (JsonArray) nextExtObj.get(next); + parseExtension(theState, jsonVal, true); + } else { + JsonValue jsonVal = nextExtObj.get(next); + parseChildren(theState, next, jsonVal, null); + } + } + theState.endingElement(); + } + } + + @Override + public T parseResource(Class theResourceType, Reader theReader) { + JsonReader reader = Json.createReader(theReader); + JsonObject object = reader.readObject(); + + JsonValue resourceTypeObj = object.get("resourceType"); + assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType"); + String resourceType = ((JsonString) resourceTypeObj).getString(); + + RuntimeResourceDefinition def; + if (theResourceType != null) { + def = myContext.getResourceDefinition(theResourceType); + } else { + def = myContext.getResourceDefinition(resourceType); + } + + ParserState state = ParserState.getPreResourceInstance(def.getImplementingClass(), myContext, true); + state.enteringNewElement(null, def.getName()); + + parseChildren(object, state); + + state.endingElement(); + + @SuppressWarnings("unchecked") + T retVal = (T) state.getObject(); + + return retVal; + } + + @Override + public T parseResource(Class theResourceType, String theMessageString) { + return parseResource(theResourceType, new StringReader(theMessageString)); + } + + @Override + public IParser setPrettyPrint(boolean thePrettyPrint) { + myPrettyPrint = thePrettyPrint; + return this; + } + private void writeAtomLink(JsonGenerator theEventWriter, String theRel, StringDt theLink) { if (isNotBlank(theLink.getValue())) { theEventWriter.writeStartObject(); @@ -823,6 +808,24 @@ public class JsonParser extends BaseParser implements IParser { } } + private void writeExtensionsAsDirectChild(IResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List extensions, + List modifierExtensions) throws IOException { + if (extensions.isEmpty() == false) { + theEventWriter.writeStartArray("extension"); + for (HeldExtension next : extensions) { + next.write(resDef, theResource, theEventWriter); + } + theEventWriter.writeEnd(); + } + if (modifierExtensions.isEmpty() == false) { + theEventWriter.writeStartArray("modifierExtension"); + for (HeldExtension next : modifierExtensions) { + next.write(resDef, theResource, theEventWriter); + } + theEventWriter.writeEnd(); + } + } + private void writeOptionalTagWithTextNode(JsonGenerator theEventWriter, String theElementName, IPrimitiveDatatype theInstantDt) { String str = theInstantDt.getValueAsString(); if (StringUtils.isNotBlank(str)) { @@ -830,14 +833,6 @@ public class JsonParser extends BaseParser implements IParser { } } - private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, StringDt theStringDt) { - if (StringUtils.isNotBlank(theStringDt.getValue())) { - theEventWriter.write(theElementName, theStringDt.getValue()); - } else { - theEventWriter.writeNull(theElementName); - } - } - private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, IdDt theIdDt) { if (StringUtils.isNotBlank(theIdDt.getValue())) { theEventWriter.write(theElementName, theIdDt.getValue()); @@ -846,10 +841,18 @@ public class JsonParser extends BaseParser implements IParser { } } + private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, StringDt theStringDt) { + if (StringUtils.isNotBlank(theStringDt.getValue())) { + theEventWriter.write(theElementName, theStringDt.getValue()); + } else { + theEventWriter.writeNull(theElementName); + } + } + private class HeldExtension { - private ExtensionDt myUndeclaredExtension; private RuntimeChildDeclaredExtensionDefinition myDef; + private ExtensionDt myUndeclaredExtension; private IElement myValue; public HeldExtension(ExtensionDt theUndeclaredExtension) { @@ -875,7 +878,9 @@ public class JsonParser extends BaseParser implements IParser { if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) { extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource); } else { - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, "value" + WordUtils.capitalize(def.getName())); +// encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, "value" + WordUtils.capitalize(def.getName())); + String childName = myDef.getChildNameByDatatype(myValue.getClass()); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName); } // theEventWriter.name(myUndeclaredExtension.get); @@ -898,9 +903,13 @@ public class JsonParser extends BaseParser implements IParser { } theEventWriter.writeEnd(); } else { - BaseRuntimeElementDefinition def = myContext.getElementDefinition(value.getClass()); - // theEventWriter.writeName("value" + def.getName()); - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, def, "value" + WordUtils.capitalize(def.getName())); + RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition(); + String childName = extDef.getChildNameByDatatype(value.getClass()); + if (childName == null) { + 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); } // theEventWriter.name(myUndeclaredExtension.get); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index 64c54827aa5..3b9b2e53c6a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -131,9 +131,9 @@ class ParserState { myState = theState; } - public static ParserState getPreAtomInstance(FhirContext theContext, boolean theJsonMode) throws DataFormatException { + public static ParserState getPreAtomInstance(FhirContext theContext, Class theResourceType, boolean theJsonMode) throws DataFormatException { ParserState retVal = new ParserState(theContext, theJsonMode); - retVal.push(retVal.new PreAtomState()); + retVal.push(retVal.new PreAtomState(theResourceType)); return retVal; } @@ -209,10 +209,12 @@ class ParserState { public class AtomEntryState extends BaseState { private BundleEntry myEntry; + private Class myResourceType; - public AtomEntryState(Bundle theInstance) { + public AtomEntryState(Bundle theInstance, Class theResourceType) { super(null); myEntry = new BundleEntry(); + myResourceType = theResourceType; theInstance.getEntries().add(myEntry); } @@ -275,7 +277,7 @@ class ParserState { } else if ("author".equals(theLocalPart)) { push(new AtomAuthorState(myEntry)); } else if ("content".equals(theLocalPart)) { - push(new PreResourceState(myEntry)); + push(new PreResourceState(myEntry, myResourceType)); } else if ("summary".equals(theLocalPart)) { push(new XhtmlState(getPreResourceState(), myEntry.getSummary(), false)); } else if ("category".equals(theLocalPart)) { @@ -387,10 +389,12 @@ class ParserState { private class AtomState extends BaseState { private Bundle myInstance; + private Class myResourceType; - public AtomState(Bundle theInstance) { + public AtomState(Bundle theInstance, Class theResourceType) { super(null); myInstance = theInstance; + myResourceType = theResourceType; } @Override @@ -401,7 +405,7 @@ class ParserState { @Override public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { if ("entry".equals(theLocalPart) && verifyNamespace(XmlParser.ATOM_NS, theNamespaceURI)) { - push(new AtomEntryState(myInstance)); + push(new AtomEntryState(myInstance, myResourceType)); } else if (theLocalPart.equals("published")) { push(new AtomPrimitiveState(myInstance.getPublished())); } else if (theLocalPart.equals("title")) { @@ -829,9 +833,11 @@ class ParserState { private class PreAtomState extends BaseState { private Bundle myInstance; + private Class myResourceType; - public PreAtomState() { + public PreAtomState(Class theResourceType) { super(null); + myResourceType = theResourceType; } @Override @@ -846,7 +852,7 @@ class ParserState { } myInstance = new Bundle(); - push(new AtomState(myInstance)); + push(new AtomState(myInstance, myResourceType)); } @@ -877,9 +883,10 @@ class ParserState { return true; } - public PreResourceState(BundleEntry theEntry) { + public PreResourceState(BundleEntry theEntry, Class theResourceType) { super(null); myEntry = theEntry; + myResourceType=theResourceType; } /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index 0a767a27d6a..8e27d9e4f35 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -193,8 +193,9 @@ public class XmlParser extends BaseParser implements IParser { } } + @Override - public Bundle parseBundle(Reader theReader) { + public Bundle parseBundle(Class theResourceType, Reader theReader) { XMLEventReader streamReader; try { streamReader = myXmlInputFactory.createXMLEventReader(theReader); @@ -204,7 +205,7 @@ public class XmlParser extends BaseParser implements IParser { throw new ConfigurationException("Failed to initialize STaX event factory", e); } - return parseBundle(streamReader); + return parseBundle(streamReader, theResourceType); } @Override @@ -590,8 +591,8 @@ public class XmlParser extends BaseParser implements IParser { } } - private Bundle parseBundle(XMLEventReader theStreamReader) { - ParserState parserState = ParserState.getPreAtomInstance(myContext, false); + private Bundle parseBundle(XMLEventReader theStreamReader, Class theResourceType) { + ParserState parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false); return doXmlLoop(theStreamReader, parserState); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index 0f1faa0efa3..f33de1fd781 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.client; +/* + * #%L + * HAPI FHIR Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 4ef499f992a..85837457736 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.client; +/* + * #%L + * HAPI FHIR Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import java.io.IOException; import java.io.Reader; import java.util.List; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java index 2a401aae748..35ed398657d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.client; +/* + * #%L + * HAPI FHIR Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.primitive.IdDt; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/FhirClientConnectionException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/FhirClientConnectionException.java index 82640be603e..7a8c5a3fd24 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/FhirClientConnectionException.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/FhirClientConnectionException.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.client.exceptions; +/* + * #%L + * HAPI FHIR Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 9408dc2a3b1..51fb7bad673 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -77,8 +77,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { private MethodReturnTypeEnum myMethodReturnType; private String myResourceName; + private Class myResourceType; - public BaseResourceReturningMethodBinding(Class theReturnResourceType, Method theMethod, FhirContext theConetxt, Object theProvider) { super(theMethod, theConetxt, theProvider); @@ -90,9 +90,11 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { } else if (Bundle.class.isAssignableFrom(methodReturnType)) { myMethodReturnType = MethodReturnTypeEnum.BUNDLE; } else { - throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); + throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + + theMethod.getDeclaringClass().getCanonicalName()); } + myResourceType = theReturnResourceType; if (theReturnResourceType != null) { ResourceDef resourceDefAnnotation = theReturnResourceType.getAnnotation(ResourceDef.class); if (resourceDefAnnotation == null) { @@ -119,7 +121,12 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { switch (getReturnType()) { case BUNDLE: { - Bundle bundle = parser.parseBundle(theResponseReader); + Bundle bundle; + if (myResourceType != null) { + bundle = parser.parseBundle(myResourceType, theResponseReader); + }else { + bundle = parser.parseBundle(theResponseReader); + } switch (getMethodReturnType()) { case BUNDLE: return bundle; @@ -138,7 +145,12 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { break; } case RESOURCE: { - IResource resource = parser.parseResource(theResponseReader); + IResource resource; + if (myResourceType != null) { + resource = parser.parseResource(myResourceType, theResponseReader); + } else { + resource = parser.parseResource(theResponseReader); + } List lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { @@ -238,7 +250,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { return (IdDt) retValObj; } } - throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName()); + throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + + IdDt.class.getCanonicalName()); } private InstantDt getInstantFromMetadataOrNullIfNone(Map theResourceMetadata, ResourceMetadataKeyEnum theKey) { @@ -254,7 +267,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { return (InstantDt) retValObj; } } - throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName()); + throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + + InstantDt.class.getCanonicalName()); } private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) { @@ -271,8 +285,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS); } - private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List theResult, EncodingUtil theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, - NarrativeModeEnum theNarrativeMode) throws IOException { + private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List theResult, EncodingUtil theResponseEncoding, String theServerBase, + String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException { assert !theServerBase.endsWith("/"); theHttpResponse.setStatus(200); @@ -317,10 +331,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { b.append(resId); /* - * If this is a history operation, we add the version of the - * resource to the self link to indicate the version + * If this is a history operation, we add the version of the resource to the self link to indicate the version */ - if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE || getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) { + if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE + || getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) { IdDt versionId = getIdFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.VERSION_ID); if (versionId != null) { b.append('/'); @@ -382,7 +396,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { } } - private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException { + private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, + boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException { theHttpResponse.setStatus(200); if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IClientResponseHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IClientResponseHandler.java index 97cabb109ae..0998eec318a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IClientResponseHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IClientResponseHandler.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.method; +/* + * #%L + * HAPI FHIR Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import java.io.IOException; import java.io.Reader; import java.util.List; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java index b75c26ae676..502188d58e6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java @@ -130,6 +130,20 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP return retVal; } + @Override + public boolean matches(Request theRequest) { + if (super.matches(theRequest)) { + if (myVersionIdParameterIndex != null) { + if (theRequest.getVersion() == null) { + return false; + } + } + return true; + } else { + return false; + } + } + @Override protected Set provideAllowableRequestTypes() { return Collections.singleton(RequestType.PUT); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index d7ac51ced0b..c88919b5b05 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -46,6 +46,7 @@ import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding; import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -74,6 +75,8 @@ public class RestfulServer extends HttpServlet { private boolean myUseBrowserFriendlyContentTypes; private ResourceBinding myNullResourceBinding = new ResourceBinding(); + private boolean myStarted; + /** * Constructor */ @@ -222,6 +225,7 @@ public class RestfulServer extends HttpServlet { throw new ServletException("Failed to initialize FHIR Restful server", ex); } + myStarted = true; ourLog.info("A FHIR has been lit on this server"); } @@ -293,7 +297,7 @@ public class RestfulServer extends HttpServlet { * {@link IllegalStateException} if called after that. */ public void setServerConformanceProvider(Object theServerConformanceProvider) { - if (myFhirContext != null) { + if (myStarted) { throw new IllegalStateException("Server is already started"); } myServerConformanceProvider = theServerConformanceProvider; @@ -524,6 +528,18 @@ public class RestfulServer extends HttpServlet { String nextString = tok.nextToken(); versionId = new IdDt(nextString); } + + if (theRequestType==RequestType.PUT && versionId==null) { + String contentLocation = theRequest.getHeader("Content-Location"); + if (contentLocation!=null) { + int idx = contentLocation.indexOf("/_history/"); + if (idx != -1) { + String versionIdString = contentLocation.substring(idx + "/_history/".length()); + versionId = new IdDt(versionIdString); + } + } + } + // TODO: look for more tokens for version, compartments, etc... diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.java index f1734e8ff31..69aaa0aee9b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.java @@ -38,6 +38,7 @@ public abstract class BaseServerResponseException extends RuntimeException { registerExceptionType(MethodNotAllowedException.STATUS_CODE, MethodNotAllowedException.class); registerExceptionType(ResourceNotFoundException.STATUS_CODE, ResourceNotFoundException.class); registerExceptionType(ResourceVersionNotSpecifiedException.STATUS_CODE, ResourceVersionNotSpecifiedException.class); + registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class); registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class); } @@ -143,5 +144,6 @@ public abstract class BaseServerResponseException extends RuntimeException { if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) { throw new Error("Can not register " + theType + " to status code " + theStatusCode + " because " + ourStatusCodeToExceptionType.get(theStatusCode) + " already registers that code"); } + ourStatusCodeToExceptionType.put(theStatusCode, theType); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/tester/PublicTesterServlet.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/tester/PublicTesterServlet.java index c2ac752f905..112226cd48a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/tester/PublicTesterServlet.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/tester/PublicTesterServlet.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.server.tester; +/* + * #%L + * HAPI FHIR Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -11,8 +31,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringEscapeUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.ContentType; import org.thymeleaf.TemplateEngine; @@ -145,7 +165,7 @@ public class PublicTesterServlet extends HttpServlet { ctx.setVariable("requestUrl", requestUrl); ctx.setVariable("action", action); ctx.setVariable("resultStatus", resultStatus); - ctx.setVariable("resultBody", StringEscapeUtils.escapeHtml(resultBody)); + ctx.setVariable("resultBody", StringEscapeUtils.escapeHtml4(resultBody)); ctx.setVariable("resultSyntaxHighlighterClass", resultSyntaxHighlighterClass); myTemplateEngine.process(PUBLIC_TESTER_RESULT_HTML, ctx, theResp.getWriter()); diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java index 5688f1c7fed..60847c3692d 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java @@ -25,6 +25,10 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.api.annotation.Extension; +import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.model.dstu.composite.AddressDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; @@ -38,6 +42,7 @@ import ca.uhn.fhir.model.dstu.resource.Specimen; import ca.uhn.fhir.model.dstu.resource.ValueSet; import ca.uhn.fhir.model.dstu.resource.ValueSet.Define; import ca.uhn.fhir.model.dstu.resource.ValueSet.DefineConcept; +import ca.uhn.fhir.model.dstu.valueset.AddressUseEnum; import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum; import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.StringDt; @@ -48,8 +53,7 @@ public class JsonParserTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParserTest.class); /** - * This sample has extra elements in that are not actually a - * part of the spec any more.. + * This sample has extra elements in that are not actually a part of the spec any more.. */ @Test public void testParseFuroreMetadataWithExtraElements() throws IOException { @@ -61,6 +65,122 @@ public class JsonParserTest { assertEquals("_id", res.getSearchParam().get(1).getName().getValue()); } + @Test + public void testEncodeExtensionWithResourceContent() throws IOException { + IParser parser = new FhirContext().newJsonParser(); + + Patient patient = new Patient(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.addUndeclaredExtension(false, "urn:foo", new ResourceReferenceDt(Organization.class, "123")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]")); + + Patient actual = parser.parseResource(Patient.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + List ext = actual.getUndeclaredExtensionsByUrl("urn:foo"); + assertEquals(1, ext.size()); + ResourceReferenceDt ref = (ResourceReferenceDt) ext.get(0).getValue(); + assertEquals("Organization/123", ref.getResourceUrl()); + + } + + @Test + public void testEncodeDeclaredExtensionWithResourceContent() throws IOException { + IParser parser = new FhirContext().newJsonParser(); + + MyPatientWithOneDeclaredExtension patient = new MyPatientWithOneDeclaredExtension(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.setFoo(new ResourceReferenceDt(Organization.class, "123")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]")); + + MyPatientWithOneDeclaredExtension actual = parser.parseResource(MyPatientWithOneDeclaredExtension.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + ResourceReferenceDt ref = actual.getFoo(); + assertEquals("Organization/123", ref.getResourceUrl()); + + } + + + @Test + public void testEncodeDeclaredExtensionWithAddressContent() throws IOException { + IParser parser = new FhirContext().newJsonParser(); + + MyPatientWithOneDeclaredAddressExtension patient = new MyPatientWithOneDeclaredAddressExtension(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.setFoo(new AddressDt().addLine("line1")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueAddress\":{\"line\":[\"line1\"]}}]")); + + MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + AddressDt ref = actual.getFoo(); + assertEquals("line1", ref.getLineFirstRep().getValue()); + + } + + + @Test + public void testEncodeUndeclaredExtensionWithAddressContent() throws IOException { + IParser parser = new FhirContext().newJsonParser(); + + Patient patient = new Patient(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.addUndeclaredExtension(false, "urn:foo", new AddressDt().addLine("line1")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueAddress\":{\"line\":[\"line1\"]}}]")); + + MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + AddressDt ref = actual.getFoo(); + assertEquals("line1", ref.getLineFirstRep().getValue()); + + } + + + @ResourceDef(name = "Patient") + public static class MyPatientWithOneDeclaredExtension extends Patient { + + @Child(order = 0, name = "foo") + @Extension(url = "urn:foo", definedLocally = true, isModifier = false) + private ResourceReferenceDt myFoo; + + public ResourceReferenceDt getFoo() { + return myFoo; + } + + public void setFoo(ResourceReferenceDt theFoo) { + myFoo = theFoo; + } + + } + + + @ResourceDef(name = "Patient") + public static class MyPatientWithOneDeclaredAddressExtension extends Patient { + + @Child(order = 0, name = "foo") + @Extension(url = "urn:foo", definedLocally = true, isModifier = false) + private AddressDt myFoo; + + public AddressDt getFoo() { + return myFoo; + } + + public void setFoo(AddressDt theFoo) { + myFoo = theFoo; + } + + } + @Test public void testEncodeExt() throws Exception { @@ -73,9 +193,9 @@ public class JsonParserTest { String encoded = new FhirContext().newJsonParser().encodeResourceToString(valueSet); ourLog.info(encoded); - + } - + @Test public void testEncodeResourceRef() throws DataFormatException, IOException { @@ -150,9 +270,6 @@ public class JsonParserTest { } - - - @Test public void testSimpleResourceEncodeWithCustomType() throws IOException { @@ -164,7 +281,7 @@ public class JsonParserTest { assertEquals("aaaa", obs.getExtAtt().getContentType().getValue()); assertEquals("str1", obs.getMoreExt().getStr1().getValue()); assertEquals("2011-01-02", obs.getModExt().getValueAsString()); - + List undeclaredExtensions = obs.getContact().get(0).getName().getFamily().get(0).getUndeclaredExtensions(); ExtensionDt undeclaredExtension = undeclaredExtensions.get(0); assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl().getValue()); @@ -185,7 +302,7 @@ public class JsonParserTest { assertEquals(expected.toString(), actual.toString()); } - + @Test public void testSimpleBundleEncode() throws IOException { @@ -239,7 +356,8 @@ public class JsonParserTest { String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeBundleToString(bundle); ourLog.info(encoded); - assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/_search?_format=application/json+fhir&search-id=46d5f0e7-9240-4d4f-9f51-f8ac975c65&search-sort=_id", bundle.getLinkSelf().getValue()); + assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/_search?_format=application/json+fhir&search-id=46d5f0e7-9240-4d4f-9f51-f8ac975c65&search-sort=_id", bundle + .getLinkSelf().getValue()); assertEquals("urn:uuid:0b754ff9-03cf-4322-a119-15019af8a3", bundle.getBundleId().getValue()); BundleEntry entry = bundle.getEntries().get(0); @@ -349,13 +467,13 @@ public class JsonParserTest { IParser newJsonParser = new FhirContext().newJsonParser(); StringReader reader = new StringReader(enc); Patient parsed = newJsonParser.parseResource(Patient.class, reader); - + ourLog.info(new FhirContext().newXmlParser().setPrettyPrint(true).encodeResourceToString(parsed)); - + assertEquals(1, parsed.getNameFirstRep().getUndeclaredExtensionsByUrl("http://examples.com#givenext").size()); ExtensionDt ext = parsed.getNameFirstRep().getUndeclaredExtensionsByUrl("http://examples.com#givenext").get(0); assertEquals("Hello", ext.getValueAsPrimitive().getValue()); - + } @Test diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java index 9d75df41567..9e38442bee9 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java @@ -31,6 +31,7 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.composite.AddressDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; @@ -51,6 +52,8 @@ import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.narrative.INarrativeGenerator; +import ca.uhn.fhir.parser.JsonParserTest.MyPatientWithOneDeclaredAddressExtension; +import ca.uhn.fhir.parser.JsonParserTest.MyPatientWithOneDeclaredExtension; public class XmlParserTest { @@ -69,7 +72,83 @@ public class XmlParserTest { } + @Test + public void testEncodeExtensionWithResourceContent() throws IOException { + IParser parser = new FhirContext().newXmlParser(); + + Patient patient = new Patient(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.addUndeclaredExtension(false, "urn:foo", new ResourceReferenceDt(Organization.class, "123")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("")); + + Patient actual = parser.parseResource(Patient.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + List ext = actual.getUndeclaredExtensionsByUrl("urn:foo"); + assertEquals(1, ext.size()); + ResourceReferenceDt ref = (ResourceReferenceDt) ext.get(0).getValue(); + assertEquals("Organization/123", ref.getResourceUrl()); + + } + + @Test + public void testEncodeDeclaredExtensionWithResourceContent() throws IOException { + IParser parser = new FhirContext().newXmlParser(); + + MyPatientWithOneDeclaredExtension patient = new MyPatientWithOneDeclaredExtension(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.setFoo(new ResourceReferenceDt(Organization.class, "123")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("")); + + MyPatientWithOneDeclaredExtension actual = parser.parseResource(MyPatientWithOneDeclaredExtension.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + ResourceReferenceDt ref = actual.getFoo(); + assertEquals("Organization/123", ref.getResourceUrl()); + + } + @Test + public void testEncodeDeclaredExtensionWithAddressContent() throws IOException { + IParser parser = new FhirContext().newXmlParser(); + + MyPatientWithOneDeclaredAddressExtension patient = new MyPatientWithOneDeclaredAddressExtension(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.setFoo(new AddressDt().addLine("line1")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("")); + + MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + AddressDt ref = actual.getFoo(); + assertEquals("line1", ref.getLineFirstRep().getValue()); + + } + + @Test + public void testEncodeUndeclaredExtensionWithAddressContent() throws IOException { + IParser parser = new FhirContext().newXmlParser(); + + Patient patient = new Patient(); + patient.addAddress().setUse(AddressUseEnum.HOME); + patient.addUndeclaredExtension(false, "urn:foo", new AddressDt().addLine("line1")); + + String val = parser.encodeResourceToString(patient); + ourLog.info(val); + assertThat(val, StringContains.containsString("")); + + MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val); + assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); + AddressDt ref = actual.getFoo(); + assertEquals("line1", ref.getLineFirstRep().getValue()); + + } @Test public void testEncodeBundleResultCount() throws IOException { diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java index d47b6113e15..de9d42ae17f 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java @@ -36,6 +36,7 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.PathSpecification; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.resource.Conformance; @@ -46,7 +47,12 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.param.CodingListParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.QualifiedDateParam; @@ -131,7 +137,7 @@ public class ClientTest { ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 400, "OK")); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 400, "foobar")); when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("foobar"), Charset.forName("UTF-8"))); @@ -481,6 +487,62 @@ public class ClientTest { } + + @Test + public void testSearchWithCustomType() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClientWithCustomType client = ctx.newRestfulClient(ITestClientWithCustomType.class, "http://foo"); + CustomPatient response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02")); + + assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString()); + assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue()); + + } + + public interface ITestClientWithCustomType extends IBasicClient { + @Search() + public CustomPatient getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate); + } + + @Test + public void testSearchWithCustomTypeList() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClientWithCustomTypeList client = ctx.newRestfulClient(ITestClientWithCustomTypeList.class, "http://foo"); + List response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02")); + + assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString()); + assertEquals("PRP1660", response.get(0).getIdentifier().get(0).getValue().getValue()); + + } + + public interface ITestClientWithCustomTypeList extends IBasicClient { + @Search() + public List getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate); + } + + @ResourceDef(name="Patient") + public static class CustomPatient extends Patient + { + // nothing + } + + @Test public void testSearchByToken() throws Exception { diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/InvalidClientDefinitionTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/InvalidClientDefinitionTest.java new file mode 100644 index 00000000000..0aa7d9c20a9 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/InvalidClientDefinitionTest.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.rest.client; + +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.client.ClientTest.CustomPatient; +import ca.uhn.fhir.rest.client.api.IBasicClient; +import ca.uhn.fhir.rest.param.QualifiedDateParam; + +public class InvalidClientDefinitionTest { + + @Test + public void testAnnotationTypeIsNotAssignableToMethodReturnType() { + // TODO: this should fail + new FhirContext().newRestfulClient(ITestClientWithCustomType.class, "http://example.com"); + } + + public interface ITestClientWithCustomType extends IBasicClient { + @Search(type=Patient.class) + public CustomPatient getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate); + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DocumentationTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DocumentationTest.java new file mode 100644 index 00000000000..dac313a14f7 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DocumentationTest.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.rest.server; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.dstu.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu.resource.Conformance; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider; + +public class DocumentationTest { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DocumentationTest.class); + + @Test + public void testSearchParameterDocumentation() throws Exception { + + RestfulServer rs = new RestfulServer(); + rs.setProviders(new SearchProvider()); + + ServerConformanceProvider sc = new ServerConformanceProvider(rs); + rs.setServerConformanceProvider(sc); + + rs.init(null); + + Conformance conformance = sc.getServerConformance(); + String conf = new FhirContext().newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + } + + /** + * Created by dsotnikov on 2/25/2014. + */ + public static class SearchProvider { + + @Search(type = Patient.class) + public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { + return null; + } + + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java index fc1abfad8f9..a1eceda0437 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java @@ -1,8 +1,6 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.io.StringReader; import java.util.ArrayList; @@ -49,6 +47,7 @@ import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.resource.Conformance; +import ca.uhn.fhir.model.dstu.resource.DiagnosticOrder; import ca.uhn.fhir.model.dstu.resource.DiagnosticReport; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Patient; @@ -860,11 +859,10 @@ public class ResfulServerMethodTest { } - @Test public void testUpdateWrongResourceType() throws Exception { // TODO: this method sends in the wrong resource type vs. the URL so it should - // give a useful error message + // give a useful error message (and then make this unit test actually run) Patient patient = new Patient(); patient.addIdentifier().setValue("002"); @@ -902,16 +900,17 @@ public class ResfulServerMethodTest { patient.getIdentifier().setValue("001"); HttpPut httpPut = new HttpPut("http://localhost:" + ourPort + "/DiagnosticReport/001"); - httpPut.addHeader("Content-Location", "/DiagnosticReport/001/_history/002"); + httpPut.addHeader("Content-Location", "/DiagnosticReport/001/_history/004"); httpPut.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); HttpResponse status = ourClient.execute(httpPut); - String responseContent = IOUtils.toString(status.getEntity().getContent()); - ourLog.info("Response was:\n{}", responseContent); +// String responseContent = IOUtils.toString(status.getEntity().getContent()); +// ourLog.info("Response was:\n{}", responseContent); - assertEquals(201, status.getStatusLine().getStatusCode()); - assertEquals("http://localhost:" + ourPort + "/DiagnosticReport/001/_history/002", status.getFirstHeader("Location").getValue()); + assertEquals(204, status.getStatusLine().getStatusCode()); + assertNull(status.getEntity()); + assertEquals("http://localhost:" + ourPort + "/DiagnosticReport/001/_history/004", status.getFirstHeader("Location").getValue()); } @@ -977,11 +976,12 @@ public class ResfulServerMethodTest { status = ourClient.execute(httpPost); - responseContent = IOUtils.toString(status.getEntity().getContent()); - ourLog.info("Response was:\n{}", responseContent); +// responseContent = IOUtils.toString(status.getEntity().getContent()); +// ourLog.info("Response was:\n{}", responseContent); - assertEquals(200, status.getStatusLine().getStatusCode()); - assertEquals("", responseContent); + assertEquals(204, status.getStatusLine().getStatusCode()); + assertNull(status.getEntity()); +// assertEquals("", responseContent); } @@ -1043,6 +1043,14 @@ public class ResfulServerMethodTest { return DiagnosticReport.class; } + @SuppressWarnings("unused") + @Update() + public MethodOutcome updateDiagnosticReportWithNoResponse(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticReport thePatient) { + IdDt id = theId; + IdDt version = theVersionId; + return new MethodOutcome(id, version); + } + @SuppressWarnings("unused") @Update() public MethodOutcome updateDiagnosticReportWithVersionAndNoResponse(@IdParam IdDt theId, @ResourceParam DiagnosticReport thePatient) { @@ -1281,10 +1289,10 @@ public class ResfulServerMethodTest { @SuppressWarnings("unused") @Update() - public MethodOutcome updateDiagnosticReportWithVersion(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticReport thePatient) { + public MethodOutcome updateDiagnosticReportWithVersion(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticOrder thePatient) { /* * TODO: THIS METHOD IS NOT USED. It's the wrong type - * (DiagnosticReport), so it should cause an exception on startup. + * (DiagnosticOrder), so it should cause an exception on startup. * Also we should detect if there are multiple resource params on an * update/create/etc method */ diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TesterTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TesterTest.java index f2fd73d753e..49df8c727cf 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TesterTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TesterTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.rest.server; +import java.lang.annotation.Documented; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -16,8 +17,10 @@ import org.junit.Test; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu.composite.QuantityDt; import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum; @@ -75,7 +78,7 @@ public class TesterTest { @Test public void testTester() throws Exception { -// if (true) return; + if (true) return; myRestfulServer.setProviders(new SearchProvider(), new GlobalHistoryProvider()); myServer.start(); @@ -111,7 +114,10 @@ public class TesterTest { } @Search(type = Patient.class) - public Patient findPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { + public Patient findPatient( + @Description(shortDefinition="The patient's identifier (MRN or other card number)") + @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier + ) { for (Patient next : getIdToPatient().values()) { for (IdentifierDt nextId : next.getIdentifier()) { if (nextId.matchesSystemAndValue(theIdentifier)) {