History operation working

This commit is contained in:
jamesagnew 2014-04-14 08:40:30 -04:00
parent 84096da74d
commit 2e5aac29d1
22 changed files with 870 additions and 192 deletions

View File

@ -1,5 +1,10 @@
package ca.uhn.fhir.model.api; 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.api.annotation.Child;
import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
@ -8,15 +13,17 @@ import ca.uhn.fhir.util.ElementUtil;
public abstract class BaseResource extends BaseElement implements IResource { public abstract class BaseResource extends BaseElement implements IResource {
@Child(name = "contained", order = 2, min = 0, max = 1)
private ContainedDt myContained;
@Child(name = "language", order = 0, min = 0, max = Child.MAX_UNLIMITED) @Child(name = "language", order = 0, min = 0, max = Child.MAX_UNLIMITED)
private CodeDt myLanguage; private CodeDt myLanguage;
private Map<ResourceMetadataKeyEnum, Object> myResourceMetadata;
@Child(name = "text", order = 1, min = 0, max = 1) @Child(name = "text", order = 1, min = 0, max = 1)
private NarrativeDt myText; private NarrativeDt myText;
@Child(name="contained", order=2, min=0, max=1)
private ContainedDt myContained;
@Override @Override
public ContainedDt getContained() { public ContainedDt getContained() {
if (myContained == null) { if (myContained == null) {
@ -25,14 +32,19 @@ public abstract class BaseResource extends BaseElement implements IResource {
return myContained; return myContained;
} }
public void setContained(ContainedDt theContained) {
myContained = theContained;
}
public CodeDt getLanguage() { public CodeDt getLanguage() {
return myLanguage; return myLanguage;
} }
@Override
public Map<ResourceMetadataKeyEnum, Object> getResourceMetadata() {
if (myResourceMetadata == null) {
myResourceMetadata = new HashMap<ResourceMetadataKeyEnum, Object>();
}
return myResourceMetadata;
}
@Override
public NarrativeDt getText() { public NarrativeDt getText() {
if (myText == null) { if (myText == null) {
myText = new NarrativeDt(); myText = new NarrativeDt();
@ -40,22 +52,32 @@ public abstract class BaseResource extends BaseElement implements IResource {
return myText; return myText;
} }
public void setContained(ContainedDt theContained) {
myContained = theContained;
}
public void setLanguage(CodeDt theLanguage) { public void setLanguage(CodeDt theLanguage) {
myLanguage = theLanguage; myLanguage = theLanguage;
} }
@Override
public void setResourceMetadata(Map<ResourceMetadataKeyEnum, Object> theMap) {
Validate.notNull(theMap, "The Map must not be null");
myResourceMetadata = theMap;
}
public void setText(NarrativeDt theText) { public void setText(NarrativeDt theText) {
myText = theText; myText = theText;
} }
/** /**
* Intended to be called by extending classes {@link #isEmpty()} implementations, returns <code>true</code> * Intended to be called by extending classes {@link #isEmpty()}
* if all content in this superclass instance is empty per the semantics of {@link #isEmpty()}. * implementations, returns <code>true</code> if all content in this
* superclass instance is empty per the semantics of {@link #isEmpty()}.
*/ */
@Override @Override
protected boolean isBaseEmpty() { protected boolean isBaseEmpty() {
return super.isBaseEmpty() && ElementUtil.isEmpty(myLanguage, myText); return super.isBaseEmpty() && ElementUtil.isEmpty(myLanguage, myText);
} }
} }

View File

@ -3,6 +3,8 @@ package ca.uhn.fhir.model.api;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.XhtmlDt; 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!!! * NB: add any new fields to the isEmpty() method!!!
*****************************************************/ *****************************************************/
//@formatter:on //@formatter:on
private StringDt myEntryId;
private StringDt myLinkSelf; private StringDt myLinkSelf;
private InstantDt myPublished; private InstantDt myPublished;
private IResource myResource; private IResource myResource;
@ -28,18 +29,11 @@ public class BundleEntry extends BaseBundle {
public boolean isEmpty() { public boolean isEmpty() {
//@formatter:off //@formatter:off
return super.isEmpty() && return super.isEmpty() &&
ElementUtil.isEmpty(myEntryId, myLinkSelf, myPublished, myResource, myTitle, myUpdated, mySummary) && ElementUtil.isEmpty(myLinkSelf, myPublished, myResource, myTitle, myUpdated, mySummary) &&
ElementUtil.isEmpty(myCategories); ElementUtil.isEmpty(myCategories);
//@formatter:on //@formatter:on
} }
public StringDt getEntryId() {
if (myEntryId == null) {
myEntryId = new StringDt();
}
return myEntryId;
}
public StringDt getLinkSelf() { public StringDt getLinkSelf() {
if (myLinkSelf == null) { if (myLinkSelf == null) {
myLinkSelf = new StringDt(); myLinkSelf = new StringDt();
@ -103,4 +97,14 @@ public class BundleEntry extends BaseBundle {
return myCategories; 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;
}
} }

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.model.api; 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.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
@ -20,4 +22,20 @@ public interface IResource extends ICompositeElement {
NarrativeDt getText(); 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<ResourceMetadataKeyEnum, Object> 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<ResourceMetadataKeyEnum, Object> theMap);
} }

View File

@ -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 <b>Published</b> time. This
* is defined by FHIR as "Time resource copied into the feed", which is generally
* best left to the current time.
* <p>
* Values for this key are of type {@link InstantDt}
* </p>
* <p>
* <b>Server Note</b>: In servers, it is generally advisable to leave this
* value <code>null</code>, in which case the server will substitute the
* current time automatically.
* </p>
*
* @see InstantDt
*/
PUBLISHED,
/**
* The value for this key is the bundle entry <b>Updated</b> time. This
* is defined by FHIR as "Last Updated for resource".
* <p>
* Values for this key are of type {@link InstantDt}
* </p>
*
* @see InstantDt
*/
UPDATED;
}

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.model.dstu.resource;
import java.util.List; import java.util.List;
import ca.uhn.fhir.model.api.BaseElement; 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.IElement;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.ResourceDef; 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; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
@ResourceDef(name="Binary", profile="http://hl7.org/fhir/profiles/Binary", id="binary") @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 // TODO: implement binary

View File

@ -90,12 +90,11 @@ public abstract class BaseParser implements IParser {
} }
/** /**
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the * If set to <code>true</code> (default is <code>false</code>), narratives
* encoded values. * will not be included in the encoded values.
*/ */
public boolean getSuppressNarratives() { public boolean getSuppressNarratives() {
return mySuppressNarratives; return mySuppressNarratives;
} }
} }

View File

@ -111,7 +111,7 @@ public class JsonParser extends BaseParser implements IParser {
eventWriter.writeStartObject(); eventWriter.writeStartObject();
writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle()); writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
writeTagWithTextNode(eventWriter, "id", nextEntry.getEntryId()); writeTagWithTextNode(eventWriter, "id", nextEntry.getId());
eventWriter.writeStartArray("link"); eventWriter.writeStartArray("link");
writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf()); 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 class HeldExtension {
private ExtensionDt myUndeclaredExtension; private ExtensionDt myUndeclaredExtension;

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.parser; 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.ArrayList;
import java.util.HashMap; 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.IResource;
import ca.uhn.fhir.model.api.IResourceBlock; import ca.uhn.fhir.model.api.IResourceBlock;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; 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.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.rest.server.Constants;
class ParserState<T extends IElement> { class ParserState<T extends IElement> {
@ -196,15 +198,54 @@ class ParserState<T extends IElement> {
@Override @Override
public void endingElement() throws DataFormatException { public void endingElement() throws DataFormatException {
populateResourceMetadata();
pop(); pop();
} }
private void populateResourceMetadata() {
if (myEntry.getResource() == null) {
return;
}
Map<ResourceMetadataKeyEnum, Object> 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 @Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
if ("title".equals(theLocalPart)) { if ("title".equals(theLocalPart)) {
push(new AtomPrimitiveState(myEntry.getTitle())); push(new AtomPrimitiveState(myEntry.getTitle()));
} else if ("id".equals(theLocalPart)) { } else if ("id".equals(theLocalPart)) {
push(new AtomPrimitiveState(myEntry.getEntryId())); push(new AtomPrimitiveState(myEntry.getId()));
} else if ("link".equals(theLocalPart)) { } else if ("link".equals(theLocalPart)) {
push(new AtomLinkState(myEntry)); push(new AtomLinkState(myEntry));
} else if ("updated".equals(theLocalPart)) { } else if ("updated".equals(theLocalPart)) {
@ -550,8 +591,7 @@ class ParserState<T extends IElement> {
} }
private class SwallowChildrenWholeState extends BaseState private class SwallowChildrenWholeState extends BaseState {
{
private int myDepth; private int myDepth;

View File

@ -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.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; 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.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.model.primitive.XhtmlDt;
@ -122,7 +123,9 @@ public class XmlParser extends BaseParser implements IParser {
eventWriter.writeStartElement("entry"); eventWriter.writeStartElement("entry");
writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle()); 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()) { if (!nextEntry.getLinkSelf().isEmpty()) {
writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf()); writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
@ -609,4 +612,12 @@ public class XmlParser extends BaseParser implements IParser {
} }
theEventWriter.writeEndElement(); 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();
}
} }

View File

@ -23,6 +23,12 @@ public class GetClientInvocation extends BaseClientInvocation {
myUrlPath = StringUtils.join(theUrlFragments, '/'); myUrlPath = StringUtils.join(theUrlFragments, '/');
} }
public GetClientInvocation(String theUrlPath) {
myParameters = Collections.emptyMap();
myUrlPath = theUrlPath;
}
public GetClientInvocation(String... theUrlFragments) { public GetClientInvocation(String... theUrlFragments) {
myParameters = Collections.emptyMap(); myParameters = Collections.emptyMap();
myUrlPath = StringUtils.join(theUrlFragments, '/'); myUrlPath = StringUtils.join(theUrlFragments, '/');

View File

@ -23,6 +23,7 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete; 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.Metadata;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
@ -84,8 +85,9 @@ public abstract class BaseMethodBinding {
Create create = theMethod.getAnnotation(Create.class); Create create = theMethod.getAnnotation(Create.class);
Update update = theMethod.getAnnotation(Update.class); Update update = theMethod.getAnnotation(Update.class);
Delete delete = theMethod.getAnnotation(Delete.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 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; return null;
} }
@ -115,6 +117,8 @@ public abstract class BaseMethodBinding {
return new UpdateMethodBinding(theMethod, theContext); return new UpdateMethodBinding(theMethod, theContext);
} else if (delete != null) { } else if (delete != null) {
return new DeleteMethodBinding(theMethod, theContext, theProvider); return new DeleteMethodBinding(theMethod, theContext, theProvider);
} else if (history != null) {
return new HistoryMethodBinding(theMethod, theContext, theProvider);
} else { } else {
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
} }

View File

@ -1,11 +1,14 @@
package ca.uhn.fhir.rest.method; package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; 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.Bundle;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource; 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.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.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.server.Constants; 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 { public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
protected static final Set<String> ALLOWED_PARAMS; protected static final Set<String> ALLOWED_PARAMS;
static {
HashSet<String> set = new HashSet<String>();
set.add(Constants.PARAM_FORMAT);
set.add(Constants.PARAM_NARRATIVE);
set.add(Constants.PARAM_PRETTY);
ALLOWED_PARAMS = Collections.unmodifiableSet(set);
}
private MethodReturnTypeEnum myMethodReturnType; private MethodReturnTypeEnum myMethodReturnType;
private String myResourceName; private String myResourceName;
public BaseResourceReturningMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt) { public BaseResourceReturningMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt) {
@ -55,12 +70,14 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
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());
} }
if (theReturnResourceType != null) {
ResourceDef resourceDefAnnotation = theReturnResourceType.getAnnotation(ResourceDef.class); ResourceDef resourceDefAnnotation = theReturnResourceType.getAnnotation(ResourceDef.class);
if (resourceDefAnnotation == null) { if (resourceDefAnnotation == null) {
throw new ConfigurationException(theReturnResourceType.getCanonicalName() + " has no @" + ResourceDef.class.getSimpleName() + " annotation"); throw new ConfigurationException(theReturnResourceType.getCanonicalName() + " has no @" + ResourceDef.class.getSimpleName() + " annotation");
} }
myResourceName = resourceDefAnnotation.name(); myResourceName = resourceDefAnnotation.name();
} }
}
public MethodReturnTypeEnum getMethodReturnType() { public MethodReturnTypeEnum getMethodReturnType() {
return myMethodReturnType; return myMethodReturnType;
@ -113,7 +130,6 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
throw new IllegalStateException("Should not get here!"); throw new IllegalStateException("Should not get here!");
} }
public abstract List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException; public abstract List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException;
@Override @Override
@ -165,13 +181,40 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
} }
} }
private IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> 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());
}
static { private InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
HashSet<String> set = new HashSet<String>(); Object retValObj = theResourceMetadata.get(theKey);
set.add(Constants.PARAM_FORMAT); if (retValObj == null) {
set.add(Constants.PARAM_NARRATIVE); return null;
set.add(Constants.PARAM_PRETTY); } else if (retValObj instanceof Date) {
ALLOWED_PARAMS = Collections.unmodifiableSet(set); 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) { private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
@ -222,7 +265,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
RuntimeResourceDefinition def = getContext().getResourceDefinition(next); RuntimeResourceDefinition def = getContext().getResourceDefinition(next);
if (next.getId() != null && StringUtils.isNotBlank(next.getId().getValue())) { 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()); entry.getTitle().setValue(def.getName() + " " + next.getId().getValue());
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
@ -230,7 +273,37 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
b.append('/'); b.append('/');
b.append(def.getName()); b.append(def.getName());
b.append('/'); 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; boolean haveQ = false;
if (thePrettyPrint) { if (thePrettyPrint) {
b.append('?').append(Constants.PARAM_PRETTY).append("=true"); 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 { private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
theHttpResponse.setStatus(200); theHttpResponse.setStatus(200);

View File

@ -1,43 +1,42 @@
package ca.uhn.fhir.rest.method; package ca.uhn.fhir.rest.method;
import java.io.IOException; import static org.apache.commons.lang3.StringUtils.*;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; 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.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.client.BaseClientInvocation; 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.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider; 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.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class HistoryMethodBinding extends BaseMethodBinding { public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
private final Integer myIdParamIndex; private final Integer myIdParamIndex;
private final RestfulOperationTypeEnum myResourceOperationType; private final RestfulOperationTypeEnum myResourceOperationType;
private final Class<? extends IResource> myType;
private final RestfulOperationSystemEnum mySystemOperationType; private final RestfulOperationSystemEnum mySystemOperationType;
private String myResourceName; private String myResourceName;
private Integer mySinceParamIndex; private Integer mySinceParamIndex;
private Integer myCountParamIndex; private Integer myCountParamIndex;
public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, IResourceProvider theProvider) { public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, IResourceProvider theProvider) {
super(theMethod, theConetxt); super(toReturnType(theMethod, theProvider), theMethod, theConetxt);
myIdParamIndex = Util.findIdParameterIndex(theMethod); myIdParamIndex = Util.findIdParameterIndex(theMethod);
mySinceParamIndex = Util.findSinceParameterIndex(theMethod); mySinceParamIndex = Util.findSinceParameterIndex(theMethod);
@ -69,14 +68,24 @@ public class HistoryMethodBinding extends BaseMethodBinding {
if (type != History.AllResources.class) { if (type != History.AllResources.class) {
myResourceName = theConetxt.getResourceDefinition(type).getName(); myResourceName = theConetxt.getResourceDefinition(type).getName();
myType = type;
} else { } else {
myResourceName = null; myResourceName = null;
myType = null;
} }
} }
private static Class<? extends IResource> toReturnType(Method theMethod, IResourceProvider theProvider) {
if (theProvider != null) {
return theProvider.getResourceType();
}
History historyAnnotation = theMethod.getAnnotation(History.class);
Class<? extends IResource> type = historyAnnotation.resourceType();
if (type != History.AllResources.class) {
return type;
}
return null;
}
@Override @Override
public RestfulOperationTypeEnum getResourceOperationType() { public RestfulOperationTypeEnum getResourceOperationType() {
return myResourceOperationType; return myResourceOperationType;
@ -89,21 +98,63 @@ public class HistoryMethodBinding extends BaseMethodBinding {
@Override @Override
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
// TODO Auto-generated method stub StringBuilder b = new StringBuilder();
return null; 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 @Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException { public ReturnTypeEnum getReturnType() {
// TODO Auto-generated method stub return ReturnTypeEnum.BUNDLE;
return null;
} }
@Override @Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException { public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException {
Object[] args = new Object[getMethod().getParameterTypes().length]; Object[] args = new Object[getMethod().getParameterTypes().length];
if (myCountParamIndex != null) { 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])) { if (countValues.length > 0 && StringUtils.isNotBlank(countValues[0])) {
try { try {
args[myCountParamIndex] = new IntegerDt(countValues[0]); args[myCountParamIndex] = new IntegerDt(countValues[0]);
@ -113,7 +164,7 @@ public class HistoryMethodBinding extends BaseMethodBinding {
} }
} }
if (mySinceParamIndex != null) { 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])) { if (sinceValues.length > 0 && StringUtils.isNotBlank(sinceValues[0])) {
try { try {
args[mySinceParamIndex] = new InstantDt(sinceValues[0]); args[mySinceParamIndex] = new InstantDt(sinceValues[0]);
@ -122,21 +173,23 @@ public class HistoryMethodBinding extends BaseMethodBinding {
} }
} }
} }
if (myIdParamIndex!=null) {
args[myIdParamIndex] = theId;
} }
@Override Object response;
public boolean matches(Request theRequest) { try {
if (!theRequest.getOperation().equals(Constants.PARAM_HISTORY)) { response = getMethod().invoke(theResourceProvider, args);
return false; } catch (IllegalAccessException e) {
} throw new InternalErrorException(e);
if (theRequest.getResourceName() == null) { } catch (IllegalArgumentException e) {
return mySystemOperationType == RestfulOperationSystemEnum.HISTORY_SYSTEM; throw new InternalErrorException(e);
} } catch (InvocationTargetException e) {
if (!theRequest.getResourceName().equals(myResourceName)) { throw new InternalErrorException(e);
return false;
} }
return false; return toResourceList(response);
} }
} }

View File

@ -48,10 +48,10 @@ public abstract class RestfulServer extends HttpServlet {
private Map<Class<? extends IResource>, IResourceProvider> myTypeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>(); private Map<Class<? extends IResource>, IResourceProvider> myTypeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
private boolean myUseBrowserFriendlyContentTypes; private boolean myUseBrowserFriendlyContentTypes;
private ISecurityManager securityManager; private ISecurityManager securityManager;
private BaseMethodBinding myServerConformanceMethod; private BaseMethodBinding myServerConformanceMethod;
public RestfulServer() { public RestfulServer() {
myFhirContext = new FhirContext();
myServerConformanceProvider=new ServerConformanceProvider(this); myServerConformanceProvider=new ServerConformanceProvider(this);
} }
@ -260,13 +260,17 @@ public abstract class RestfulServer extends HttpServlet {
if (tok.hasMoreTokens()) { if (tok.hasMoreTokens()) {
String nextString = tok.nextToken(); String nextString = tok.nextToken();
if (nextString.startsWith(Constants.PARAM_HISTORY)) { if (nextString.startsWith("_")) {
if (operation !=null) {
throw new InvalidRequestException("URL Path contains two operations (part beginning with _): " + requestPath);
}
operation = nextString;
}
}
if (tok.hasMoreTokens()) { if (tok.hasMoreTokens()) {
versionId = new IdDt(tok.nextToken()); String nextString = tok.nextToken();
} else { versionId = new IdDt(nextString);
throw new InvalidRequestException("_history search specified but no version requested in URL");
}
}
} }
// TODO: look for more tokens for version, compartments, etc... // TODO: look for more tokens for version, compartments, etc...
@ -347,7 +351,6 @@ public abstract class RestfulServer extends HttpServlet {
ourLog.info("Got {} resource providers", myTypeToProvider.size()); ourLog.info("Got {} resource providers", myTypeToProvider.size());
myFhirContext = new FhirContext(myTypeToProvider.keySet());
myFhirContext.setNarrativeGenerator(myNarrativeGenerator); myFhirContext.setNarrativeGenerator(myNarrativeGenerator);
for (IResourceProvider provider : myTypeToProvider.values()) { for (IResourceProvider provider : myTypeToProvider.values()) {

View File

@ -7,8 +7,10 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification; 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.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt; 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.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Create; 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.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.Metadata;
@ -82,6 +85,24 @@ public void deletePatient(@IdParam IdDt theId) {
//END SNIPPET: delete //END SNIPPET: delete
//START SNIPPET: history
@History()
public List<Patient> getPatientHistory(@IdParam IdDt theId) {
List<Patient> retVal = new ArrayList<Patient>();
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 //START SNIPPET: vread
@Read() @Read()
public Patient getResourceById(@IdParam IdDt theId, public Patient getResourceById(@IdParam IdDt theId,
@ -310,8 +331,7 @@ return retVal;
public static void main(String[] args) throws DataFormatException, IOException { public static void main(String[] args) throws DataFormatException, IOException {
//nothing
} }
@ -348,6 +368,24 @@ public interface MetadataClient extends IRestfulClient {
} }
//END SNIPPET: metadataClient //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 { public void bbbbb() throws DataFormatException, IOException {
//START SNIPPET: metadataClientUsage //START SNIPPET: metadataClientUsage
FhirContext ctx = new FhirContext(); FhirContext ctx = new FhirContext();
@ -358,6 +396,9 @@ System.out.println(ctx.newXmlParser().encodeResourceToString(metadata));
} }
} }

View File

@ -71,7 +71,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#instance_history">Instance - History</a> <a href="#history">Instance - History</a>
</td> </td>
<td> <td>
Retrieve the update history for a particular resource Retrieve the update history for a particular resource
@ -89,7 +89,7 @@
<td> <td>
<a href="#instance_update">Type - Search</a> <a href="#instance_update">Type - Search</a>
<macro name="toc"> <macro name="toc">
<param name="section" value="8"/> <param name="section" value="7"/>
<param name="fromDepth" value="2"/> <param name="fromDepth" value="2"/>
</macro> </macro>
</td> </td>
@ -107,7 +107,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#type_history">Type - History</a> <a href="#history">Type - History</a>
</td> </td>
<td> <td>
Retrieve the update history for a particular resource type Retrieve the update history for a particular resource type
@ -115,7 +115,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#type_history">Type - Validate</a> <a href="#type_validate">Type - Validate</a>
</td> </td>
<td> <td>
Check that the content would be acceptable as an update Check that the content would be acceptable as an update
@ -139,7 +139,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#system_history">System - History</a> <a href="#history">System - History</a>
</td> </td>
<td> <td>
Retrieve the update history for all resources Retrieve the update history for all resources
@ -157,6 +157,7 @@
</tbody> </tbody>
</table> </table>
<a name="instance_read"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -164,7 +165,6 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="Instance Level - Read"> <section name="Instance Level - Read">
<a name="instance_read"/>
<p> <p>
The The
@ -186,6 +186,7 @@
<code>http://fhir.example.com/Patient/111</code> <code>http://fhir.example.com/Patient/111</code>
</p> </p>
<a name="instance_vread"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -193,7 +194,6 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="Instance Level - VRead"> <section name="Instance Level - VRead">
<a name="instance_vread"/>
<p> <p>
The The
@ -215,6 +215,7 @@
<code>http://fhir.example.com/Patient/111/_history/2</code> <code>http://fhir.example.com/Patient/111/_history/2</code>
</p> </p>
<a name="instance_update"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -222,7 +223,6 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="Instance Level - Update"> <section name="Instance Level - Update">
<a name="instance_update"/>
<p> <p>
The The
@ -283,6 +283,7 @@
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" /> <param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro> </macro>
<a name="instance_delete"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -290,7 +291,6 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="Instance Level - Delete"> <section name="Instance Level - Delete">
<a name="instance_delete"/>
<p> <p>
The The
@ -329,19 +329,7 @@
<code>http://fhir.example.com/Patient/111</code> <code>http://fhir.example.com/Patient/111</code>
</p> </p>
</section> <a name="type_create"/>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Instance Level - History">
<a name="instance_history"/>
<p>
Not yet implemented
</p>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -349,7 +337,6 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="Type Level - Create"> <section name="Type Level - Create">
<a name="type_create"/>
<p> <p>
The The
@ -394,6 +381,7 @@
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" /> <param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro> </macro>
<a name="type_search"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -401,7 +389,6 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="Type Level - Search"> <section name="Type Level - Search">
<a name="type_search"/>
<p> <p>
The The
@ -698,19 +685,7 @@
</subsection> </subsection>
</section> <a name="type_validate"/>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Type Level - History">
<a name="type_history"/>
<p>
Not yet implemented
</p>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -718,12 +693,12 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="Type Level - Validate"> <section name="Type Level - Validate">
<a name="type_validate"/>
<p> <p>
Not yet implemented Not yet implemented
</p> </p>
<a name="system_conformance"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -731,7 +706,6 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="System Level - Conformance"> <section name="System Level - Conformance">
<a name="system_conformance"/>
<p> <p>
FHIR defines that a FHIR Server must be able to export a conformance statement, FHIR defines that a FHIR Server must be able to export a conformance statement,
@ -779,6 +753,7 @@
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" /> <param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro> </macro>
<a name="system_transaction"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -786,25 +761,12 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="System Level - Transaction"> <section name="System Level - Transaction">
<a name="system_transaction"/>
<p>
Not yet implemented
</p>
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="System Level - History">
<a name="system_history"/>
<p> <p>
Not yet implemented Not yet implemented
</p> </p>
<a name="system_search"/>
</section> </section>
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
@ -812,14 +774,71 @@
<!-- ****************************************************************** --> <!-- ****************************************************************** -->
<section name="System Level - Search"> <section name="System Level - Search">
<a name="system_search"/>
<p> <p>
Not yet implemented Not yet implemented
</p> </p>
<a name="history"/>
</section> </section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="History (Instance, Type, Server)">
<p>
The
<a href="http://hl7.org/implement/standards/fhir/http.html#history"><b>history</b></a>
operation retrieves a historical collection of all versions of a single resource
<i>(instance history)</i>, all resources of a given type <i>(type history)</i>,
or all resources of any type on a server <i>(server history)</i>.
</p>
<p>
History methods must be annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/History.html">@History</a>
annotation, and will have additional requirements depending on the kind
of history method intended:
</p>
<ul>
<li>
For an <b>Instance History</b> method, the method must have a parameter
annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
annotation, indicating the ID of the resource for which to return history.
</li>
<li>
For an <b>Type History</b> method, the method must not have any @IdParam parameter.
</li>
<li>
For an <b>Server History</b> method, the method must not have any @IdParam parameter
and must not be found in a ResourceProvider definition.
<!-- TODO: make ResourceProvider a link to a defintion of these on the RESTFul server page -->
</li>
</ul>
<p>
The following snippet shows how to define a history method on a server:
</p>
<macro name="snippet">
<param name="id" value="history" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
The following snippet shows how to define various history methods in a client.
</p>
<macro name="snippet">
<param name="id" value="historyClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</section>
</body> </body>

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.context;
import java.util.List; import java.util.List;
import ca.uhn.fhir.model.api.BaseResource;
import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IExtension; import ca.uhn.fhir.model.api.IExtension;
import ca.uhn.fhir.model.api.IResource; 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; import ca.uhn.fhir.model.primitive.StringDt;
@ResourceDef(name = "ResourceWithExtensionsA", id="0001") @ResourceDef(name = "ResourceWithExtensionsA", id="0001")
public class ResourceWithExtensionsA implements IResource { public class ResourceWithExtensionsA extends BaseResource {
/* /*
* NB: several unit tests depend on the structure here * NB: several unit tests depend on the structure here

View File

@ -224,7 +224,7 @@ public class JsonParserTest {
assertEquals("urn:uuid:0b754ff9-03cf-4322-a119-15019af8a3", bundle.getBundleId().getValue()); assertEquals("urn:uuid:0b754ff9-03cf-4322-a119-15019af8a3", bundle.getBundleId().getValue());
BundleEntry entry = bundle.getEntries().get(0); 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("http://fhir.healthintersections.com.au/open/DiagnosticReport/101/_history/1", entry.getLinkSelf().getValue());
assertEquals("2014-03-10T11:55:59Z", entry.getUpdated().getValueAsString()); assertEquals("2014-03-10T11:55:59Z", entry.getUpdated().getValueAsString());

View File

@ -440,7 +440,7 @@ public class XmlParserTest {
BundleEntry entry = bundle.getEntries().get(0); BundleEntry entry = bundle.getEntries().get(0);
assertEquals("HL7, Inc (FHIR Project)", entry.getAuthorName().getValue()); 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(); ValueSet resource = (ValueSet) entry.getResource();
assertEquals("LOINC Codes for Cholesterol", resource.getName().getValue()); assertEquals("LOINC Codes for Cholesterol", resource.getName().getValue());

View File

@ -6,6 +6,7 @@ import static org.mockito.Mockito.*;
import java.io.StringReader; import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.commons.io.IOUtils; 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.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; 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.PathSpecification;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Conformance; 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.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.IdDt; 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.StringDt;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.CodingListParam; import ca.uhn.fhir.rest.param.CodingListParam;
@ -64,6 +68,168 @@ public class ClientTest {
httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
} }
@Test
public void testHistoryResourceInstance() throws Exception {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><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 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> 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 = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><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 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> 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 = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><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 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> 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 @Test
public void testRead() throws Exception { public void testRead() throws Exception {

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete; 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.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.OptionalParam;
@ -28,26 +29,8 @@ import ca.uhn.fhir.rest.param.QualifiedDateParam;
public interface ITestClient extends IBasicClient { public interface ITestClient extends IBasicClient {
@Read(type=Patient.class) @Create
Patient getPatientById(@IdParam IdDt theId); public MethodOutcome createPatient(@ResourceParam Patient thePatient);
@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<Patient> getPatientMultipleIdentifiers(@RequiredParam(name = "ids") CodingListParam theIdentifiers);
@Search() @Search()
public List<Patient> getPatientByDateRange(@RequiredParam(name = "dateRange") DateRangeParam theIdentifiers); public List<Patient> getPatientByDateRange(@RequiredParam(name = "dateRange") DateRangeParam theIdentifiers);
@ -56,7 +39,7 @@ public interface ITestClient extends IBasicClient {
public List<Patient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate); public List<Patient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
@Search() @Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam List<PathSpecification> theIncludes); public List<Patient> getPatientMultipleIdentifiers(@RequiredParam(name = "ids") CodingListParam theIdentifiers);
@Search(queryName="someQueryNoParams") @Search(queryName="someQueryNoParams")
public Patient getPatientNoParams(); public Patient getPatientNoParams();
@ -64,13 +47,40 @@ public interface ITestClient extends IBasicClient {
@Search(queryName="someQueryOneParam") @Search(queryName="someQueryOneParam")
public Patient getPatientOneParam(@RequiredParam(name="param1") StringDt theParam); public Patient getPatientOneParam(@RequiredParam(name="param1") StringDt theParam);
@Create @Search()
public MethodOutcome createPatient(@ResourceParam Patient thePatient); public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam List<PathSpecification> theIncludes);
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient);
@Update @Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @ResourceParam Patient thePatient); public MethodOutcome updatePatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @ResourceParam Patient thePatient);
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @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);
} }

View File

@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -28,6 +29,7 @@ import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.core.IsNot; import org.hamcrest.core.IsNot;
import org.hamcrest.core.StringContains; import org.hamcrest.core.StringContains;
import org.hamcrest.core.StringEndsWith;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; 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.BundleEntry;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification; 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.CodingDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt; 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.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum; import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt; 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.StringDt;
import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete; 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.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.OptionalParam;
@ -90,7 +95,8 @@ public class ResfulServerMethodTest {
DummyDiagnosticReportResourceProvider reportProvider = new DummyDiagnosticReportResourceProvider(); DummyDiagnosticReportResourceProvider reportProvider = new DummyDiagnosticReportResourceProvider();
ServletHandler proxyHandler = new ServletHandler(); 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, "/*"); proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler); ourServer.setHandler(proxyHandler);
ourServer.start(); ourServer.start();
@ -161,7 +167,7 @@ public class ResfulServerMethodTest {
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
BundleEntry entry0 = bundle.getEntries().get(0); BundleEntry entry0 = bundle.getEntries().get(0);
assertEquals("http://localhost:" + ourPort + "/Patient/1", entry0.getLinkSelf().getValue()); 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"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?withIncludes=include1&_include=include2&_include=include3&_format=json");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
@ -404,6 +410,110 @@ public class ResfulServerMethodTest {
} }
@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 @Test
public void testCreate() throws Exception { public void testCreate() throws Exception {
@ -765,6 +875,7 @@ public class ResfulServerMethodTest {
} }
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
*/ */
@ -773,16 +884,7 @@ public class ResfulServerMethodTest {
public Map<String, Patient> getIdToPatient() { public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>(); Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{ {
Patient patient = new Patient(); Patient patient = createPatient1();
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");
idToPatient.put("1", patient); idToPatient.put("1", patient);
} }
{ {
@ -801,6 +903,67 @@ public class ResfulServerMethodTest {
return idToPatient; 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<Patient> getHistoryResourceType() {
ArrayList<Patient> retVal = new ArrayList<Patient>();
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<Patient> getHistoryResourceInstance(@IdParam IdDt theId) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
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") @SuppressWarnings("unused")
@Update() @Update()
public MethodOutcome updateDiagnosticReportWithVersion(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticReport thePatient) { public MethodOutcome updateDiagnosticReportWithVersion(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticReport thePatient) {