diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java index 08af71f2d16..51d207c8a09 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java @@ -1,5 +1,10 @@ package ca.uhn.fhir.model.api; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.Validate; + import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; @@ -8,14 +13,16 @@ import ca.uhn.fhir.util.ElementUtil; public abstract class BaseResource extends BaseElement implements IResource { - @Child(name="language", order=0, min=0, max=Child.MAX_UNLIMITED) + @Child(name = "contained", order = 2, min = 0, max = 1) + private ContainedDt myContained; + + @Child(name = "language", order = 0, min = 0, max = Child.MAX_UNLIMITED) private CodeDt myLanguage; - @Child(name="text", order=1, min=0, max=1) - private NarrativeDt myText; + private Map myResourceMetadata; - @Child(name="contained", order=2, min=0, max=1) - private ContainedDt myContained; + @Child(name = "text", order = 1, min = 0, max = 1) + private NarrativeDt myText; @Override public ContainedDt getContained() { @@ -25,14 +32,19 @@ public abstract class BaseResource extends BaseElement implements IResource { return myContained; } - public void setContained(ContainedDt theContained) { - myContained = theContained; - } - public CodeDt getLanguage() { return myLanguage; } + @Override + public Map getResourceMetadata() { + if (myResourceMetadata == null) { + myResourceMetadata = new HashMap(); + } + return myResourceMetadata; + } + + @Override public NarrativeDt getText() { if (myText == null) { myText = new NarrativeDt(); @@ -40,22 +52,32 @@ public abstract class BaseResource extends BaseElement implements IResource { return myText; } + public void setContained(ContainedDt theContained) { + myContained = theContained; + } + public void setLanguage(CodeDt theLanguage) { myLanguage = theLanguage; } + @Override + public void setResourceMetadata(Map theMap) { + Validate.notNull(theMap, "The Map must not be null"); + myResourceMetadata = theMap; + } + public void setText(NarrativeDt theText) { myText = theText; } - + /** - * Intended to be called by extending classes {@link #isEmpty()} implementations, returns true - * if all content in this superclass instance is empty per the semantics of {@link #isEmpty()}. + * Intended to be called by extending classes {@link #isEmpty()} + * implementations, returns true if all content in this + * superclass instance is empty per the semantics of {@link #isEmpty()}. */ @Override protected boolean isBaseEmpty() { return super.isBaseEmpty() && ElementUtil.isEmpty(myLanguage, myText); } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java index c6c0c3d7227..ad543650164 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java @@ -3,6 +3,8 @@ package ca.uhn.fhir.model.api; import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.Validate; + import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; @@ -15,7 +17,6 @@ public class BundleEntry extends BaseBundle { * NB: add any new fields to the isEmpty() method!!! *****************************************************/ //@formatter:on - private StringDt myEntryId; private StringDt myLinkSelf; private InstantDt myPublished; private IResource myResource; @@ -28,17 +29,10 @@ public class BundleEntry extends BaseBundle { public boolean isEmpty() { //@formatter:off return super.isEmpty() && - ElementUtil.isEmpty(myEntryId, myLinkSelf, myPublished, myResource, myTitle, myUpdated, mySummary) && + ElementUtil.isEmpty(myLinkSelf, myPublished, myResource, myTitle, myUpdated, mySummary) && ElementUtil.isEmpty(myCategories); //@formatter:on } - - public StringDt getEntryId() { - if (myEntryId == null) { - myEntryId = new StringDt(); - } - return myEntryId; - } public StringDt getLinkSelf() { if (myLinkSelf == null) { @@ -103,4 +97,14 @@ public class BundleEntry extends BaseBundle { return myCategories; } + public void setPublished(InstantDt thePublished) { + Validate.notNull(thePublished, "Published may not be null"); + myPublished = thePublished; + } + + public void setUpdated(InstantDt theUpdated) { + Validate.notNull(theUpdated, "Updated may not be null"); + myUpdated = theUpdated; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java index 84bb3caacda..55c7f73b029 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.model.api; +import java.util.Map; + import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; @@ -20,4 +22,20 @@ public interface IResource extends ICompositeElement { NarrativeDt getText(); + /** + * Returns the metadata map for this object, creating it if neccesary. Metadata + * entries are used to get/set feed bundle entries, such as the + * resource version, or the last updated timestamp. + */ + Map getResourceMetadata(); + + /** + * Sets the metadata map for this object. Metadata + * entries are used to get/set feed bundle entries, such as the + * resource version, or the last updated timestamp. + * + * @throws NullPointerException The map must not be null + */ + void setResourceMetadata(Map theMap); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java new file mode 100644 index 00000000000..92bbf9aead2 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java @@ -0,0 +1,37 @@ +package ca.uhn.fhir.model.api; + +import ca.uhn.fhir.model.primitive.InstantDt; + +public enum ResourceMetadataKeyEnum { + + VERSION_ID, + + /** + * The value for this key is the bundle entry Published time. This + * is defined by FHIR as "Time resource copied into the feed", which is generally + * best left to the current time. + *

+ * Values for this key are of type {@link InstantDt} + *

+ *

+ * Server Note: In servers, it is generally advisable to leave this + * value null, in which case the server will substitute the + * current time automatically. + *

+ * + * @see InstantDt + */ + PUBLISHED, + + /** + * The value for this key is the bundle entry Updated time. This + * is defined by FHIR as "Last Updated for resource". + *

+ * Values for this key are of type {@link InstantDt} + *

+ * + * @see InstantDt + */ + UPDATED; + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Binary.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Binary.java index c6d4fd074bf..92b1657a7a9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Binary.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Binary.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.model.dstu.resource; import java.util.List; import ca.uhn.fhir.model.api.BaseElement; +import ca.uhn.fhir.model.api.BaseResource; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.annotation.ResourceDef; @@ -10,7 +11,7 @@ import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; @ResourceDef(name="Binary", profile="http://hl7.org/fhir/profiles/Binary", id="binary") -public class Binary extends BaseElement implements IResource { +public class Binary extends BaseResource implements IResource { // TODO: implement binary 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 babbcef8f86..a0c70cb60d6 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 @@ -75,7 +75,7 @@ public abstract class BaseParser implements IParser { b.append(type); b.append(" but this is not a valid type for this element"); if (nextChild instanceof RuntimeChildChoiceDefinition) { - RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition)nextChild; + RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild; b.append(" - Expected one of: " + choice.getValidChildTypes()); } throw new DataFormatException(b.toString()); @@ -85,17 +85,16 @@ public abstract class BaseParser implements IParser { @Override public IParser setSuppressNarratives(boolean theSuppressNarratives) { - mySuppressNarratives=theSuppressNarratives; + mySuppressNarratives = theSuppressNarratives; return this; } /** - * If set to true (default is false), narratives will not be included in the - * encoded values. + * If set to true (default is false), narratives + * will not be included in the encoded values. */ public boolean getSuppressNarratives() { return mySuppressNarratives; } - } 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 4a7de9a02ca..4f7477d24a8 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 @@ -111,7 +111,7 @@ public class JsonParser extends BaseParser implements IParser { eventWriter.writeStartObject(); writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle()); - writeTagWithTextNode(eventWriter, "id", nextEntry.getEntryId()); + writeTagWithTextNode(eventWriter, "id", nextEntry.getId()); eventWriter.writeStartArray("link"); writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf()); @@ -818,6 +818,14 @@ public class JsonParser extends BaseParser implements IParser { } } + private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, IdDt theIdDt) { + if (StringUtils.isNotBlank(theIdDt.getValue())) { + theEventWriter.write(theElementName, theIdDt.getValue()); + } else { + theEventWriter.writeNull(theElementName); + } + } + private class HeldExtension { private ExtensionDt myUndeclaredExtension; 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 3a41f47be1c..d7a54353141 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 @@ -1,6 +1,6 @@ package ca.uhn.fhir.parser; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; import java.util.ArrayList; import java.util.HashMap; @@ -36,10 +36,12 @@ import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResourceBlock; import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.XhtmlDt; +import ca.uhn.fhir.rest.server.Constants; class ParserState { @@ -196,15 +198,54 @@ class ParserState { @Override public void endingElement() throws DataFormatException { + populateResourceMetadata(); pop(); } + private void populateResourceMetadata() { + if (myEntry.getResource() == null) { + return; + } + + Map metadata = myEntry.getResource().getResourceMetadata(); + if (myEntry.getPublished().isEmpty() == false) { + metadata.put(ResourceMetadataKeyEnum.PUBLISHED, myEntry.getPublished()); + } + if (myEntry.getUpdated().isEmpty() == false) { + metadata.put(ResourceMetadataKeyEnum.UPDATED, myEntry.getUpdated()); + } + if (!myEntry.getLinkSelf().isEmpty()) { + String subStr = "/" + Constants.PARAM_HISTORY + "/"; + String linkSelfValue = myEntry.getLinkSelf().getValue(); + int startIndex = linkSelfValue.indexOf(subStr); + if (startIndex > 0) { + startIndex = startIndex + subStr.length(); + int endIndex = linkSelfValue.indexOf('?', startIndex); + if (endIndex == -1) { + endIndex = linkSelfValue.length(); + } + String versionId = linkSelfValue.substring(startIndex, endIndex); + if (isNotBlank(versionId)) { + int idx = versionId.indexOf('/'); + if (idx != -1) { + // Just in case + ourLog.warn("Bundle entry link-self contains path information beyond version (this will be ignored): {}", versionId); + versionId = versionId.substring(0, idx); + } + metadata.put(ResourceMetadataKeyEnum.VERSION_ID, versionId); + + } + } + } + + } + @Override public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { if ("title".equals(theLocalPart)) { push(new AtomPrimitiveState(myEntry.getTitle())); } else if ("id".equals(theLocalPart)) { - push(new AtomPrimitiveState(myEntry.getEntryId())); + push(new AtomPrimitiveState(myEntry.getId())); } else if ("link".equals(theLocalPart)) { push(new AtomLinkState(myEntry)); } else if ("updated".equals(theLocalPart)) { @@ -550,8 +591,7 @@ class ParserState { } - private class SwallowChildrenWholeState extends BaseState - { + private class SwallowChildrenWholeState extends BaseState { private int myDepth; @@ -571,9 +611,9 @@ class ParserState { public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { myDepth++; } - + } - + private class ElementCompositeState extends BaseState { private BaseRuntimeElementCompositeDefinition myDefinition; @@ -669,7 +709,7 @@ class ParserState { if (values == null || values.isEmpty() || values.get(0) == null) { newDt = targetElem.newInstance(); child.getMutator().addValue(myInstance, newDt); - }else { + } else { newDt = (ContainedDt) values.get(0); } ContainedResourcesState state = new ContainedResourcesState(getPreResourceState()); 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 14ab8787fac..d81dbe8f9d4 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 @@ -46,6 +46,7 @@ import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; @@ -122,7 +123,9 @@ public class XmlParser extends BaseParser implements IParser { eventWriter.writeStartElement("entry"); writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle()); - writeTagWithTextNode(eventWriter, "id", nextEntry.getEntryId()); + writeTagWithTextNode(eventWriter, "id", nextEntry.getId()); + writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated()); + writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished()); if (!nextEntry.getLinkSelf().isEmpty()) { writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf()); @@ -609,4 +612,12 @@ public class XmlParser extends BaseParser implements IParser { } theEventWriter.writeEndElement(); } + + private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, IdDt theIdDt) throws XMLStreamException { + theEventWriter.writeStartElement(theElementName); + if (StringUtils.isNotBlank(theIdDt.getValue())) { + theEventWriter.writeCharacters(theIdDt.getValue()); + } + theEventWriter.writeEndElement(); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GetClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GetClientInvocation.java index 6b80e9e49b8..839bce47fd7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GetClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GetClientInvocation.java @@ -22,7 +22,13 @@ public class GetClientInvocation extends BaseClientInvocation { myParameters = theParameters; myUrlPath = StringUtils.join(theUrlFragments, '/'); } + + public GetClientInvocation(String theUrlPath) { + myParameters = Collections.emptyMap(); + myUrlPath = theUrlPath; + } + public GetClientInvocation(String... theUrlFragments) { myParameters = Collections.emptyMap(); myUrlPath = StringUtils.join(theUrlFragments, '/'); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java index 09cc661d5c8..ffb6d04a30a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Search; @@ -84,8 +85,9 @@ public abstract class BaseMethodBinding { Create create = theMethod.getAnnotation(Create.class); Update update = theMethod.getAnnotation(Update.class); Delete delete = theMethod.getAnnotation(Delete.class); + History history = theMethod.getAnnotation(History.class); // ** if you add another annotation above, also add it to the next line: - if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance,create,update,delete)) { + if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance,create,update,delete,history)) { return null; } @@ -115,6 +117,8 @@ public abstract class BaseMethodBinding { return new UpdateMethodBinding(theMethod, theContext); } else if (delete != null) { return new DeleteMethodBinding(theMethod, theContext, theProvider); + } else if (history != null) { + return new HistoryMethodBinding(theMethod, theContext, theProvider); } else { throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); } 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 d09db976bbc..677d9821302 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 @@ -1,11 +1,14 @@ package ca.uhn.fhir.rest.method; +import static org.apache.commons.lang3.StringUtils.*; + import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -22,8 +25,12 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; +import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; import ca.uhn.fhir.rest.server.Constants; @@ -38,7 +45,15 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { protected static final Set ALLOWED_PARAMS; + static { + HashSet set = new HashSet(); + set.add(Constants.PARAM_FORMAT); + set.add(Constants.PARAM_NARRATIVE); + set.add(Constants.PARAM_PRETTY); + ALLOWED_PARAMS = Collections.unmodifiableSet(set); + } private MethodReturnTypeEnum myMethodReturnType; + private String myResourceName; public BaseResourceReturningMethodBinding(Class theReturnResourceType, Method theMethod, FhirContext theConetxt) { @@ -55,11 +70,13 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); } - ResourceDef resourceDefAnnotation = theReturnResourceType.getAnnotation(ResourceDef.class); - if (resourceDefAnnotation == null) { - throw new ConfigurationException(theReturnResourceType.getCanonicalName() + " has no @" + ResourceDef.class.getSimpleName() + " annotation"); + if (theReturnResourceType != null) { + ResourceDef resourceDefAnnotation = theReturnResourceType.getAnnotation(ResourceDef.class); + if (resourceDefAnnotation == null) { + throw new ConfigurationException(theReturnResourceType.getCanonicalName() + " has no @" + ResourceDef.class.getSimpleName() + " annotation"); + } + myResourceName = resourceDefAnnotation.name(); } - myResourceName = resourceDefAnnotation.name(); } public MethodReturnTypeEnum getMethodReturnType() { @@ -73,7 +90,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi public abstract ReturnTypeEnum getReturnType(); @Override - public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode,Map> theHeaders) throws IOException { + public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException { IParser parser = createAppropriateParser(theResponseMimeType, theResponseReader, theResponseStatusCode); switch (getReturnType()) { @@ -113,7 +130,6 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi throw new IllegalStateException("Should not get here!"); } - public abstract List invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, InternalErrorException; @Override @@ -165,15 +181,42 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi } } - - static { - HashSet set = new HashSet(); - set.add(Constants.PARAM_FORMAT); - set.add(Constants.PARAM_NARRATIVE); - set.add(Constants.PARAM_PRETTY); - ALLOWED_PARAMS = Collections.unmodifiableSet(set); + private IdDt getIdFromMetadataOrNullIfNone(Map theResourceMetadata, ResourceMetadataKeyEnum theKey) { + Object retValObj = theResourceMetadata.get(theKey); + if (retValObj == null) { + return null; + } else if (retValObj instanceof String) { + if (isNotBlank((String) retValObj)) { + return new IdDt((String) retValObj); + } else { + return null; + } + } else if (retValObj instanceof IdDt) { + if (((IdDt) retValObj).isEmpty()) { + return null; + } else { + 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()); } - + + private InstantDt getInstantFromMetadataOrNullIfNone(Map theResourceMetadata, ResourceMetadataKeyEnum theKey) { + Object retValObj = theResourceMetadata.get(theKey); + if (retValObj == null) { + return null; + } else if (retValObj instanceof Date) { + return new InstantDt((Date) retValObj); + } else if (retValObj instanceof InstantDt) { + if (((InstantDt) retValObj).isEmpty()) { + return null; + } else { + 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()); + } + private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) { IParser parser; switch (theResponseEncoding) { @@ -205,7 +248,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi theHttpResponse.setCharacterEncoding("UTF-8"); theServer.addHapiHeader(theHttpResponse); - + Bundle bundle = new Bundle(); bundle.getAuthorName().setValue(getClass().getCanonicalName()); bundle.getBundleId().setValue(UUID.randomUUID().toString()); @@ -222,7 +265,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi RuntimeResourceDefinition def = getContext().getResourceDefinition(next); if (next.getId() != null && StringUtils.isNotBlank(next.getId().getValue())) { - entry.getEntryId().setValue(next.getId().getValue()); + entry.getId().setValue(next.getId().getValue()); entry.getTitle().setValue(def.getName() + " " + next.getId().getValue()); StringBuilder b = new StringBuilder(); @@ -230,7 +273,37 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi b.append('/'); b.append(def.getName()); b.append('/'); - b.append(next.getId().getValue()); + String resId = next.getId().getValue(); + 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 (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('/'); + b.append(Constants.PARAM_HISTORY); + b.append('/'); + b.append(versionId.getValue()); + } else { + throw new InternalErrorException("Server did not provide a VERSION_ID in the resource metadata for resource with ID " + resId); + } + } + + InstantDt published = getInstantFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.PUBLISHED); + if (published == null) { + entry.getPublished().setToCurrentTimeInLocalTimeZone(); + } else { + entry.setPublished(published); + } + + InstantDt updated = getInstantFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.UPDATED); + if (updated != null) { + entry.setUpdated(updated); + } + boolean haveQ = false; if (thePrettyPrint) { b.append('?').append(Constants.PARAM_PRETTY).append("=true"); @@ -269,7 +342,6 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi } } - private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException { theHttpResponse.setStatus(200); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java index e4dce42aeed..8020acae637 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java @@ -1,43 +1,42 @@ package ca.uhn.fhir.rest.method; -import java.io.IOException; -import java.io.Reader; +import static org.apache.commons.lang3.StringUtils.*; + +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletResponse; - +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; +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.parser.DataFormatException; import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.client.BaseClientInvocation; +import ca.uhn.fhir.rest.client.GetClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -public class HistoryMethodBinding extends BaseMethodBinding { +public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { private final Integer myIdParamIndex; private final RestfulOperationTypeEnum myResourceOperationType; - private final Class myType; private final RestfulOperationSystemEnum mySystemOperationType; private String myResourceName; private Integer mySinceParamIndex; private Integer myCountParamIndex; public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, IResourceProvider theProvider) { - super(theMethod, theConetxt); + super(toReturnType(theMethod, theProvider), theMethod, theConetxt); myIdParamIndex = Util.findIdParameterIndex(theMethod); mySinceParamIndex = Util.findSinceParameterIndex(theMethod); @@ -69,14 +68,24 @@ public class HistoryMethodBinding extends BaseMethodBinding { if (type != History.AllResources.class) { myResourceName = theConetxt.getResourceDefinition(type).getName(); - myType = type; } else { myResourceName = null; - myType = null; } } + private static Class toReturnType(Method theMethod, IResourceProvider theProvider) { + if (theProvider != null) { + return theProvider.getResourceType(); + } + History historyAnnotation = theMethod.getAnnotation(History.class); + Class type = historyAnnotation.resourceType(); + if (type != History.AllResources.class) { + return type; + } + return null; + } + @Override public RestfulOperationTypeEnum getResourceOperationType() { return myResourceOperationType; @@ -89,21 +98,63 @@ public class HistoryMethodBinding extends BaseMethodBinding { @Override public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { - // TODO Auto-generated method stub - return null; + StringBuilder b = new StringBuilder(); + if (myResourceName!=null) { + b.append(myResourceName); + if (myIdParamIndex!=null) { + IdDt id = (IdDt)theArgs[myIdParamIndex]; + if (id==null||isBlank(id.getValue())) { + throw new NullPointerException("ID can not be null"); + } + b.append('/'); + b.append(id.getValue()); + } + } + if (b.length()>0) { + b.append('/'); + } + b.append(Constants.PARAM_HISTORY); + + return new GetClientInvocation(b.toString()); + } + + + @SuppressWarnings("deprecation") // ObjectUtils.equals is replaced by a JDK7 method.. + @Override + public boolean matches(Request theRequest) { + if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) { + return false; + } + if (theRequest.getResourceName() == null) { + return mySystemOperationType == RestfulOperationSystemEnum.HISTORY_SYSTEM; + } + if (!ObjectUtils.equals(theRequest.getResourceName(),myResourceName)) { + return false; + } + + boolean haveIdParam= theRequest.getId() != null && !theRequest.getId().isEmpty(); + boolean wantIdParam = myIdParamIndex != null; + if (haveIdParam!=wantIdParam) { + return false; + } + + if (theRequest.getVersion() != null && !theRequest.getVersion().isEmpty()) { + return false; + } + + return true; } @Override - public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException { - // TODO Auto-generated method stub - return null; + public ReturnTypeEnum getReturnType() { + return ReturnTypeEnum.BUNDLE; } @Override - public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException { + public List invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, InternalErrorException { Object[] args = new Object[getMethod().getParameterTypes().length]; if (myCountParamIndex != null) { - String[] countValues = theRequest.getParameters().remove(Constants.PARAM_COUNT); + String[] countValues = theParameterValues.remove(Constants.PARAM_COUNT); if (countValues.length > 0 && StringUtils.isNotBlank(countValues[0])) { try { args[myCountParamIndex] = new IntegerDt(countValues[0]); @@ -113,7 +164,7 @@ public class HistoryMethodBinding extends BaseMethodBinding { } } if (mySinceParamIndex != null) { - String[] sinceValues = theRequest.getParameters().remove(Constants.PARAM_SINCE); + String[] sinceValues = theParameterValues.remove(Constants.PARAM_SINCE); if (sinceValues.length > 0 && StringUtils.isNotBlank(sinceValues[0])) { try { args[mySinceParamIndex] = new InstantDt(sinceValues[0]); @@ -122,21 +173,23 @@ public class HistoryMethodBinding extends BaseMethodBinding { } } } - } - @Override - public boolean matches(Request theRequest) { - if (!theRequest.getOperation().equals(Constants.PARAM_HISTORY)) { - return false; + if (myIdParamIndex!=null) { + args[myIdParamIndex] = theId; } - if (theRequest.getResourceName() == null) { - return mySystemOperationType == RestfulOperationSystemEnum.HISTORY_SYSTEM; + + Object response; + try { + response = getMethod().invoke(theResourceProvider, args); + } catch (IllegalAccessException e) { + throw new InternalErrorException(e); + } catch (IllegalArgumentException e) { + throw new InternalErrorException(e); + } catch (InvocationTargetException e) { + throw new InternalErrorException(e); } - if (!theRequest.getResourceName().equals(myResourceName)) { - return false; - } - - return false; + + return toResourceList(response); } } 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 4af39f4454c..bdf8d202946 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 @@ -48,10 +48,10 @@ public abstract class RestfulServer extends HttpServlet { private Map, IResourceProvider> myTypeToProvider = new HashMap, IResourceProvider>(); private boolean myUseBrowserFriendlyContentTypes; private ISecurityManager securityManager; - private BaseMethodBinding myServerConformanceMethod; public RestfulServer() { + myFhirContext = new FhirContext(); myServerConformanceProvider=new ServerConformanceProvider(this); } @@ -260,15 +260,19 @@ public abstract class RestfulServer extends HttpServlet { if (tok.hasMoreTokens()) { String nextString = tok.nextToken(); - if (nextString.startsWith(Constants.PARAM_HISTORY)) { - if (tok.hasMoreTokens()) { - versionId = new IdDt(tok.nextToken()); - } else { - throw new InvalidRequestException("_history search specified but no version requested in URL"); + if (nextString.startsWith("_")) { + if (operation !=null) { + throw new InvalidRequestException("URL Path contains two operations (part beginning with _): " + requestPath); } + operation = nextString; } } + if (tok.hasMoreTokens()) { + String nextString = tok.nextToken(); + versionId = new IdDt(nextString); + } + // TODO: look for more tokens for version, compartments, etc... Request r = new Request(); @@ -347,7 +351,6 @@ public abstract class RestfulServer extends HttpServlet { ourLog.info("Got {} resource providers", myTypeToProvider.size()); - myFhirContext = new FhirContext(myTypeToProvider.keySet()); myFhirContext.setNarrativeGenerator(myNarrativeGenerator); for (IResourceProvider provider : myTypeToProvider.values()) { diff --git a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java index 2fbab06a08c..ad6fad9bc5e 100644 --- a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java +++ b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java @@ -7,8 +7,10 @@ import java.util.List; import java.util.Set; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.PathSpecification; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; @@ -22,6 +24,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.Metadata; @@ -82,6 +85,24 @@ public void deletePatient(@IdParam IdDt theId) { //END SNIPPET: delete +//START SNIPPET: history +@History() +public List getPatientHistory(@IdParam IdDt theId) { + List retVal = new ArrayList(); + + Patient patient = new Patient(); + patient.addName().addFamily("Smith"); + + // Set the ID and version + patient.setId(theId); + patient.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("1")); + + // ...populate the rest... + return retVal; +} +//END SNIPPET: history + + //START SNIPPET: vread @Read() public Patient getResourceById(@IdParam IdDt theId, @@ -310,8 +331,7 @@ return retVal; public static void main(String[] args) throws DataFormatException, IOException { - - +//nothing } @@ -348,6 +368,24 @@ public interface MetadataClient extends IRestfulClient { } //END SNIPPET: metadataClient +public interface HistoryClient { +//START SNIPPET: historyClient +// Server level (history of ALL resources) +@History +Bundle getHistoryServer(); + +// Type level (history of all resources of a given type) +@History(resourceType=Patient.class) +Bundle getHistoryPatientType(); + +// Instance level (history of a specific resource instance by type and ID) +@History(resourceType=Patient.class) +Bundle getHistoryPatientInstance(@IdParam IdDt theId); +//END SNIPPET: historyClient + +} + + public void bbbbb() throws DataFormatException, IOException { //START SNIPPET: metadataClientUsage FhirContext ctx = new FhirContext(); @@ -358,6 +396,9 @@ System.out.println(ctx.newXmlParser().encodeResourceToString(metadata)); } + + + } diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml index d46b5f48a97..f86098b96b7 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml @@ -71,7 +71,7 @@ - Instance - History + Instance - History Retrieve the update history for a particular resource @@ -89,7 +89,7 @@ Type - Search - + @@ -107,7 +107,7 @@ - Type - History + Type - History Retrieve the update history for a particular resource type @@ -115,7 +115,7 @@ - Type - Validate + Type - Validate Check that the content would be acceptable as an update @@ -139,7 +139,7 @@ - System - History + System - History Retrieve the update history for all resources @@ -157,6 +157,7 @@ + @@ -164,7 +165,6 @@
-

The @@ -186,6 +186,7 @@ http://fhir.example.com/Patient/111

+
@@ -193,7 +194,6 @@
-

The @@ -215,6 +215,7 @@ http://fhir.example.com/Patient/111/_history/2

+
@@ -222,7 +223,6 @@
-

The @@ -283,6 +283,7 @@ +

@@ -290,7 +291,6 @@
-

The @@ -329,19 +329,7 @@ http://fhir.example.com/Patient/111

-
- - - - - -
- - -

- Not yet implemented -

- +
@@ -349,7 +337,6 @@
-

The @@ -394,6 +381,7 @@ +

@@ -401,7 +389,6 @@
-

The @@ -698,19 +685,7 @@ -

- - - - - -
- - -

- Not yet implemented -

- +
@@ -718,12 +693,12 @@
-

Not yet implemented

+
@@ -731,7 +706,6 @@
-

FHIR defines that a FHIR Server must be able to export a conformance statement, @@ -779,6 +753,7 @@ +

@@ -786,25 +761,12 @@
- - -

- Not yet implemented -

- -
- - - - - -
-

Not yet implemented

+
@@ -812,14 +774,71 @@
-

Not yet implemented

+
+ + + + +
+ +

+ The + history + operation retrieves a historical collection of all versions of a single resource + (instance history), all resources of a given type (type history), + or all resources of any type on a server (server history). +

+

+ History methods must be annotated with the + @History + annotation, and will have additional requirements depending on the kind + of history method intended: +

+
    +
  • + For an Instance History method, the method must have a parameter + annotated with the + @IdParam + annotation, indicating the ID of the resource for which to return history. +
  • +
  • + For an Type History method, the method must not have any @IdParam parameter. +
  • +
  • + For an Server History method, the method must not have any @IdParam parameter + and must not be found in a ResourceProvider definition. + +
  • +
+

+ The following snippet shows how to define a history method on a server: +

+ + + + + + + + + +

+ The following snippet shows how to define various history methods in a client. +

+ + + + + + +
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/context/ResourceWithExtensionsA.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/context/ResourceWithExtensionsA.java index cf32d5c5ee1..8079407f67b 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/context/ResourceWithExtensionsA.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/context/ResourceWithExtensionsA.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.context; import java.util.List; +import ca.uhn.fhir.model.api.BaseResource; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IExtension; import ca.uhn.fhir.model.api.IResource; @@ -17,7 +18,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; @ResourceDef(name = "ResourceWithExtensionsA", id="0001") -public class ResourceWithExtensionsA implements IResource { +public class ResourceWithExtensionsA extends BaseResource { /* * NB: several unit tests depend on the structure here 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 5b8129f06c6..eeadaae281e 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 @@ -224,7 +224,7 @@ public class JsonParserTest { assertEquals("urn:uuid:0b754ff9-03cf-4322-a119-15019af8a3", bundle.getBundleId().getValue()); BundleEntry entry = bundle.getEntries().get(0); - assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101", entry.getEntryId().getValue()); + assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101", entry.getId().getValue()); assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101/_history/1", entry.getLinkSelf().getValue()); assertEquals("2014-03-10T11:55:59Z", entry.getUpdated().getValueAsString()); 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 3a4d63621a4..9d75df41567 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 @@ -440,7 +440,7 @@ public class XmlParserTest { BundleEntry entry = bundle.getEntries().get(0); assertEquals("HL7, Inc (FHIR Project)", entry.getAuthorName().getValue()); - assertEquals("http://hl7.org/fhir/valueset/256a5231-a2bb-49bd-9fea-f349d428b70d", entry.getEntryId().getValue()); + assertEquals("http://hl7.org/fhir/valueset/256a5231-a2bb-49bd-9fea-f349d428b70d", entry.getId().getValue()); ValueSet resource = (ValueSet) entry.getResource(); assertEquals("LOINC Codes for Cholesterol", resource.getName().getValue()); 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 e2745aa9633..3ff21f74805 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 @@ -6,6 +6,7 @@ import static org.mockito.Mockito.*; import java.io.StringReader; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.Date; import java.util.List; import org.apache.commons.io.IOUtils; @@ -29,7 +30,9 @@ import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; 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.PathSpecification; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.resource.Conformance; @@ -37,6 +40,7 @@ import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.param.CodingListParam; @@ -64,6 +68,168 @@ public class ClientTest { httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); } + @Test + public void testHistoryResourceInstance() throws Exception { + + //@formatter:off + String msg = "<id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 2222221969-12-31T19:00:20.000-05:001969-12-31T19:00:10.000-05:00Patient 2222221969-12-31T19:00:30.000-05:001969-12-31T19:00:10.000-05:00"; + //@formatter:on + + 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_ATOM_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + Bundle response = client.getHistoryPatientInstance(new IdDt("111")); + + assertEquals("http://foo/Patient/111/_history", capt.getValue().getURI().toString()); + + assertEquals(2, response.getEntries().size()); + + // Older resource + { + BundleEntry olderEntry = response.getEntries().get(0); + assertEquals("222", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + // Newer resource + { + BundleEntry newerEntry = response.getEntries().get(1); + assertEquals("222", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + } + + @Test + public void testHistoryResourceType() throws Exception { + + //@formatter:off + String msg = "<id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 2222221969-12-31T19:00:20.000-05:001969-12-31T19:00:10.000-05:00Patient 2222221969-12-31T19:00:30.000-05:001969-12-31T19:00:10.000-05:00"; + //@formatter:on + + 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_ATOM_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + Bundle response = client.getHistoryPatientType(); + + assertEquals("http://foo/Patient/_history", capt.getValue().getURI().toString()); + + assertEquals(2, response.getEntries().size()); + + // Older resource + { + BundleEntry olderEntry = response.getEntries().get(0); + assertEquals("222", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + // Newer resource + { + BundleEntry newerEntry = response.getEntries().get(1); + assertEquals("222", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + } + + @Test + public void testHistoryServer() throws Exception { + + //@formatter:off + String msg = "<id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 2222221969-12-31T19:00:20.000-05:001969-12-31T19:00:10.000-05:00Patient 2222221969-12-31T19:00:30.000-05:001969-12-31T19:00:10.000-05:00"; + //@formatter:on + + 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_ATOM_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + Bundle response = client.getHistoryServer(); + + assertEquals("http://foo/_history", capt.getValue().getURI().toString()); + + assertEquals(2, response.getEntries().size()); + + // Older resource + { + BundleEntry olderEntry = response.getEntries().get(0); + assertEquals("222", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + // Newer resource + { + BundleEntry newerEntry = response.getEntries().get(1); + assertEquals("222", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + } + @Test public void testRead() throws Exception { diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java index fddc196fd2e..cbd90322588 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java @@ -11,6 +11,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.OptionalParam; @@ -28,35 +29,17 @@ import ca.uhn.fhir.rest.param.QualifiedDateParam; public interface ITestClient extends IBasicClient { - @Read(type=Patient.class) - Patient getPatientById(@IdParam IdDt theId); - - @Delete(resourceType=Patient.class) - MethodOutcome deletePatient(@IdParam IdDt theId); - - @Delete(resourceType=DiagnosticReport.class) - void deleteDiagnosticReport(@IdParam IdDt theId); - - @Read(type=Patient.class) - Patient getPatientByVersionId(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId); - - @Search(type=Patient.class) - Patient findPatientByMrn(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theId); - - @Search(type=Patient.class) - Bundle findPatientByName(@RequiredParam(name = Patient.SP_FAMILY) StringDt theId, @OptionalParam(name=Patient.SP_GIVEN) StringDt theGiven); - - @Search() - public List getPatientMultipleIdentifiers(@RequiredParam(name = "ids") CodingListParam theIdentifiers); + @Create + public MethodOutcome createPatient(@ResourceParam Patient thePatient); @Search() public List getPatientByDateRange(@RequiredParam(name = "dateRange") DateRangeParam theIdentifiers); @Search() public List getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate); - + @Search() - public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam List theIncludes); + public List getPatientMultipleIdentifiers(@RequiredParam(name = "ids") CodingListParam theIdentifiers); @Search(queryName="someQueryNoParams") public Patient getPatientNoParams(); @@ -64,13 +47,40 @@ public interface ITestClient extends IBasicClient { @Search(queryName="someQueryOneParam") public Patient getPatientOneParam(@RequiredParam(name="param1") StringDt theParam); - @Create - public MethodOutcome createPatient(@ResourceParam Patient thePatient); - + @Search() + public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam List theIncludes); + + @Update + public MethodOutcome updatePatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @ResourceParam Patient thePatient); + @Update public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient); - @Update - public MethodOutcome updatePatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @ResourceParam Patient thePatient); + @Delete(resourceType=DiagnosticReport.class) + void deleteDiagnosticReport(@IdParam IdDt theId); + + @Delete(resourceType=Patient.class) + MethodOutcome deletePatient(@IdParam IdDt theId); + + @Search(type=Patient.class) + Patient findPatientByMrn(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theId); + + @Search(type=Patient.class) + Bundle findPatientByName(@RequiredParam(name = Patient.SP_FAMILY) StringDt theId, @OptionalParam(name=Patient.SP_GIVEN) StringDt theGiven); + + @History(resourceType=Patient.class) + Bundle getHistoryPatientInstance(@IdParam IdDt theId); + + @History(resourceType=Patient.class) + Bundle getHistoryPatientType(); + + @History + Bundle getHistoryServer(); + + @Read(type=Patient.class) + Patient getPatientById(@IdParam IdDt theId); + + @Read(type=Patient.class) + Patient getPatientByVersionId(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId); } 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 1c4b7bab347..e5d7cabf3af 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 @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,6 +29,7 @@ import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hamcrest.core.IsNot; import org.hamcrest.core.StringContains; +import org.hamcrest.core.StringEndsWith; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -37,6 +39,7 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.PathSpecification; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; @@ -46,11 +49,13 @@ import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.OptionalParam; @@ -90,7 +95,8 @@ public class ResfulServerMethodTest { DummyDiagnosticReportResourceProvider reportProvider = new DummyDiagnosticReportResourceProvider(); ServletHandler proxyHandler = new ServletHandler(); - ServletHolder servletHolder = new ServletHolder(new DummyRestfulServer(patientProvider, profProvider,reportProvider)); + DummyRestfulServer servlet = new DummyRestfulServer(patientProvider, profProvider,reportProvider); + ServletHolder servletHolder = new ServletHolder(servlet); proxyHandler.addServletWithMapping(servletHolder, "/*"); ourServer.setHandler(proxyHandler); ourServer.start(); @@ -161,7 +167,7 @@ public class ResfulServerMethodTest { Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); BundleEntry entry0 = bundle.getEntries().get(0); assertEquals("http://localhost:" + ourPort + "/Patient/1", entry0.getLinkSelf().getValue()); - assertEquals("1", entry0.getEntryId().getValue()); + assertEquals("1", entry0.getId().getValue()); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?withIncludes=include1&_include=include2&_include=include3&_format=json"); status = ourClient.execute(httpGet); @@ -403,7 +409,111 @@ public class ResfulServerMethodTest { assertEquals(2, bundle.getEntries().size()); } + + + @Test + public void testHistoryResourceType() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history"); + HttpResponse status = ourClient.execute(httpGet); + + String responseContent = IOUtils.toString(status.getEntity().getContent()); + ourLog.info("Response was:\n{}", responseContent); + + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + + assertEquals(2, bundle.getEntries().size()); + + // Older resource + { + BundleEntry olderEntry = bundle.getEntries().get(0); + assertEquals("1", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/1/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + // Newer resource + { + BundleEntry newerEntry = bundle.getEntries().get(1); + assertEquals("1", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/1/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + + + } + + + @Test + public void testHistoryResourceInstance() throws Exception { + + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/222/_history"); + HttpResponse status = ourClient.execute(httpGet); + + String responseContent = IOUtils.toString(status.getEntity().getContent()); + ourLog.info("Response was:\n{}", responseContent); + + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + + assertEquals(2, bundle.getEntries().size()); + + // Older resource + { + BundleEntry olderEntry = bundle.getEntries().get(0); + assertEquals("222", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + // Newer resource + { + BundleEntry newerEntry = bundle.getEntries().get(1); + assertEquals("222", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + } + + + } + + + @Test public void testCreate() throws Exception { @@ -765,6 +875,7 @@ public class ResfulServerMethodTest { } + /** * Created by dsotnikov on 2/25/2014. */ @@ -773,16 +884,7 @@ public class ResfulServerMethodTest { public Map getIdToPatient() { Map idToPatient = new HashMap(); { - Patient patient = new Patient(); - patient.addIdentifier(); - patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); - patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); - patient.getIdentifier().get(0).setValue("00001"); - patient.addName(); - patient.getName().get(0).addFamily("Test"); - patient.getName().get(0).addGiven("PatientOne"); - patient.getGender().setText("M"); - patient.getId().setValue("1"); + Patient patient = createPatient1(); idToPatient.put("1", patient); } { @@ -801,6 +903,67 @@ public class ResfulServerMethodTest { return idToPatient; } + private Patient createPatient1() { + Patient patient = new Patient(); + patient.addIdentifier(); + patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); + patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); + patient.getIdentifier().get(0).setValue("00001"); + patient.addName(); + patient.getName().get(0).addFamily("Test"); + patient.getName().get(0).addGiven("PatientOne"); + patient.getGender().setText("M"); + patient.getId().setValue("1"); + return patient; + } + + @History + public List getHistoryResourceType() { + ArrayList retVal = new ArrayList(); + + Patient older = createPatient1(); + older.getNameFirstRep().getFamilyFirstRep().setValue("OlderFamily"); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "1"); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new Date(10000L)); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt(new Date(20000L))); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "1"); + retVal.add(older); + + Patient newer = createPatient1(); + newer.getNameFirstRep().getFamilyFirstRep().setValue("NewerFamily"); + newer.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "2"); + newer.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new Date(10000L)); + newer.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt(new Date(30000L))); + retVal.add(newer); + + return retVal; + } + + + @History + public List getHistoryResourceInstance(@IdParam IdDt theId) { + ArrayList retVal = new ArrayList(); + + Patient older = createPatient1(); + older.setId(theId); + older.getNameFirstRep().getFamilyFirstRep().setValue("OlderFamily"); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "1"); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new Date(10000L)); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt(new Date(20000L))); + older.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "1"); + retVal.add(older); + + Patient newer = createPatient1(); + newer.setId(theId); + newer.getNameFirstRep().getFamilyFirstRep().setValue("NewerFamily"); + newer.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, "2"); + newer.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new Date(10000L)); + newer.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt(new Date(30000L))); + retVal.add(newer); + + return retVal; + } + @SuppressWarnings("unused") @Update() public MethodOutcome updateDiagnosticReportWithVersion(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticReport thePatient) {