From a2a1035003acb369fa3f106841abad8784dc7b73 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 11 Jul 2014 18:27:27 -0400 Subject: [PATCH] More work on tester --- hapi-fhir-base/src/changes/changes.xml | 9 ++ .../java/ca/uhn/fhir/model/api/Bundle.java | 9 +- .../ca/uhn/fhir/model/api/BundleEntry.java | 65 ++++++-- .../BaseThymeleafNarrativeGenerator.java | 6 +- .../java/ca/uhn/fhir/parser/ParserState.java | 50 +++++- .../java/ca/uhn/fhir/parser/XmlParser.java | 69 ++++++--- .../ca/uhn/fhir/rest/client/BaseClient.java | 14 +- .../uhn/fhir/rest/client/GenericClient.java | 22 ++- .../ca/uhn/fhir/rest/gclient/StringParam.java | 13 ++ .../fhir/rest/method/SearchMethodBinding.java | 10 +- .../uhn/fhir/rest/param/SearchParameter.java | 11 ++ .../uhn/fhir/narrative/DiagnosticReport.html | 2 +- ...efaultThymeleafNarrativeGeneratorTest.java | 10 +- .../ca/uhn/fhir/parser/XmlParserTest.java | 10 +- .../fhir/rest/client/GenericClientTest.java | 44 ++++++ .../fhir/rest/server/StringParameterTest.java | 35 ++++- .../WEB-INF/hapi-fhir-tester-config.xml | 2 +- .../main/java/ca/uhn/fhir/to/Controller.java | 144 ++++++++++++++---- .../WEB-INF/hapi-fhir-tester-config.xml | 3 +- .../webapp/WEB-INF/templates/resource.html | 6 + .../main/webapp/WEB-INF/templates/result.html | 2 - .../WEB-INF/templates/tmpl-queries.html | 1 + .../src/main/webapp/css/tester.css | 9 ++ .../main/webapp/js/ClientCodeGeneratorHapi.js | 65 +++++++- .../src/main/webapp/js/RestfulTester.js | 105 +++++++------ 25 files changed, 573 insertions(+), 143 deletions(-) diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml index 708f37275308..93ce76babb75 100644 --- a/hapi-fhir-base/src/changes/changes.xml +++ b/hapi-fhir-base/src/changes/changes.xml @@ -71,6 +71,15 @@ Server now automatically compresses responses if the client indicates support + + Server failed to support optional parameters when type is String and :exact qualifier is used + + + Read method in client correctly populated resource ID in returned object + + + Support added for deleted-entry by/name, by/email, and comment from Tombstones spec + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java index 9d7678a3aec7..a1b5b5231cf5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java @@ -58,13 +58,12 @@ public class Bundle extends BaseBundle /* implements IElement */{ private IntegerDt myTotalResults; private InstantDt myUpdated; + /** + * Returns true if this bundle contains zero entries + */ @Override public boolean isEmpty() { - //@formatter:off - return super.isEmpty() && - ElementUtil.isEmpty(myBundleId, myLinkBase, myLinkFirst, myLinkLast, myLinkNext, myLinkPrevious, myLinkSelf, myPublished, myTitle, myTotalResults) && - ElementUtil.isEmpty(myEntries); - //@formatter:on + return getEntries().isEmpty(); } /** 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 85709ee0189f..469f1b9db37f 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 @@ -38,6 +38,9 @@ public class BundleEntry extends BaseBundle { //@formatter:on private TagList myCategories; private InstantDt myDeletedAt; + private StringDt myDeletedByEmail; + private StringDt myDeletedByName; + private StringDt myDeletedComment; private StringDt myLinkAlternate; private StringDt myLinkSelf; private InstantDt myPublished; @@ -52,18 +55,6 @@ public Tag addCategory() { return retVal; } - @Override - public String toString() { - ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); - if (getResource() != null) { - b.append("type", getResource().getClass().getSimpleName()); - } else { - b.append("No resource"); - } - b.append("id", getId()); - return b.toString(); - } - public void addCategory(Tag theTag) { getCategories().add(theTag); } @@ -85,6 +76,27 @@ public InstantDt getDeletedAt() { return myDeletedAt; } + public StringDt getDeletedByEmail() { + if (myDeletedByEmail == null) { + myDeletedByEmail = new StringDt(); + } + return myDeletedByEmail; + } + + public StringDt getDeletedByName() { + if (myDeletedByName == null) { + myDeletedByName = new StringDt(); + } + return myDeletedByName; + } + + public StringDt getDeletedComment() { + if (myDeletedComment == null) { + myDeletedComment = new StringDt(); + } + return myDeletedComment; + } + public StringDt getLinkAlternate() { if (myLinkAlternate == null) { myLinkAlternate = new StringDt(); @@ -135,7 +147,7 @@ public InstantDt getUpdated() { public boolean isEmpty() { //@formatter:off return super.isEmpty() && - ElementUtil.isEmpty(myCategories, myDeletedAt, myLinkAlternate, myLinkSelf, myPublished, myResource, mySummary, myTitle, myUpdated); + ElementUtil.isEmpty(myCategories, myDeletedAt, myLinkAlternate, myLinkSelf, myPublished, myResource, mySummary, myTitle, myUpdated, myDeletedByEmail, myDeletedByName, myDeletedComment); //@formatter:on } @@ -146,6 +158,21 @@ public void setDeleted(InstantDt theDeletedAt) { myDeletedAt = theDeletedAt; } + public void setDeletedByEmail(StringDt theDeletedByEmail) { + myDeletedByEmail = theDeletedByEmail; + } + + public void setDeletedByName(StringDt theDeletedByName) { + if (myDeletedByName == null) { + myDeletedByName = new StringDt(); + } + myDeletedByName = theDeletedByName; + } + + public void setDeletedComment(StringDt theDeletedComment) { + myDeletedComment = theDeletedComment; + } + public void setLinkAlternate(StringDt theLinkAlternate) { myLinkAlternate = theLinkAlternate; } @@ -171,4 +198,16 @@ public void setUpdated(InstantDt theUpdated) { myUpdated = theUpdated; } + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + if (getResource() != null) { + b.append("type", getResource().getClass().getSimpleName()); + } else { + b.append("No resource"); + } + b.append("id", getId()); + return b.toString(); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java index 6b9b27770b40..b29988a70876 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java @@ -525,10 +525,14 @@ protected ProcessorResult processAttribute(Arguments theArguments, Element theEl final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue); final Object value = expression.execute(configuration, theArguments); - + theElement.removeAttribute(theAttributeName); theElement.clearChildren(); + if (value == null) { + return ProcessorResult.ok(); + } + Context context = new Context(); context.setVariable("resource", value); 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 408e5cd5f59b..b73e435a87c2 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 @@ -157,8 +157,7 @@ public boolean verifyNamespace(String theExpect, String theActual) { } /** - * Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically - * intended for embedded XHTML content + * Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically intended for embedded XHTML content */ public void xmlEvent(XMLEvent theNextEvent) { myState.xmlEvent(theNextEvent); @@ -238,8 +237,7 @@ public void attributeValue(String theName, String theValue) throws DataFormatExc myInstance.setScheme(theValue); } else if ("value".equals(theName)) { /* - * This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values - * instead of one like everything else. + * This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values instead of one like everything else. */ switch (myCatState) { case STATE_LABEL: @@ -299,6 +297,23 @@ public void attributeValue(String theName, String theValue) throws DataFormatExc } } + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if ("by".equals(theLocalPart) && verifyNamespace(XmlParser.TOMBSTONES_NS, theNamespaceURI)) { + push(new AtomDeletedEntryByState(getEntry())); + } else if ("comment".equals(theLocalPart)) { + push(new AtomPrimitiveState(getEntry().getDeletedComment())); + } else if ("link".equals(theLocalPart)) { + push(new AtomLinkState(getEntry())); + } else { + if (theNamespaceURI != null) { + throw new DataFormatException("Unexpected element: {" + theNamespaceURI + "}" + theLocalPart); + } else { + throw new DataFormatException("Unexpected element: " + theLocalPart); + } + } + } + @Override public void endingElement() throws DataFormatException { putPlacerResourceInDeletedEntry(getEntry()); @@ -307,6 +322,33 @@ public void endingElement() throws DataFormatException { } + public class AtomDeletedEntryByState extends BaseState { + + private BundleEntry myEntry; + + public AtomDeletedEntryByState(BundleEntry theEntry) { + super(null); + myEntry = theEntry; + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if ("name".equals(theLocalPart)) { + push(new AtomPrimitiveState(myEntry.getDeletedByName())); + } else if ("email".equals(theLocalPart)) { + push(new AtomPrimitiveState(myEntry.getDeletedByEmail())); + } else { + throw new DataFormatException("Unexpected element in entry: " + theLocalPart); + } + } + + @Override + public void endingElement() throws DataFormatException { + pop(); + } + + } + private class AtomDeletedJsonWhenState extends BaseState { private String myData; 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 7d4283bba329..1c31b747bd5d 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 @@ -143,13 +143,32 @@ public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws Data } for (BundleEntry nextEntry : theBundle.getEntries()) { - boolean deleted=false; - if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty()==false) { - deleted=true; - eventWriter.writeStartElement("at","deleted-entry",TOMBSTONES_NS); + boolean deleted = false; + if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) { + deleted = true; + eventWriter.writeStartElement("at", "deleted-entry", TOMBSTONES_NS); eventWriter.writeNamespace("at", TOMBSTONES_NS); eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString()); eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString()); + if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty()) { + eventWriter.writeStartElement(TOMBSTONES_NS, "by"); + if (nextEntry.getDeletedByName().isEmpty()==false) { + eventWriter.writeStartElement(TOMBSTONES_NS, "name"); + eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue()); + eventWriter.writeEndElement(); + } + if (nextEntry.getDeletedByEmail().isEmpty() == false) { + eventWriter.writeStartElement(TOMBSTONES_NS, "email"); + eventWriter.writeCharacters(nextEntry.getDeletedByEmail().getValue()); + eventWriter.writeEndElement(); + } + eventWriter.writeEndElement(); + } + if (nextEntry.getDeletedComment().isEmpty()==false) { + eventWriter.writeStartElement(TOMBSTONES_NS, "comment"); + eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue()); + eventWriter.writeEndElement(); + } } else { eventWriter.writeStartElement("entry"); } @@ -377,8 +396,8 @@ private T doXmlLoop(XMLEventReader streamReader, ParserState parserState) } } - private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName, BaseRuntimeElementDefinition childDef, String theExtensionUrl, boolean theIncludedResource) - throws XMLStreamException, DataFormatException { + private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName, + BaseRuntimeElementDefinition childDef, String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException { if (nextValue.isEmpty()) { return; } @@ -442,8 +461,8 @@ private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDe } - private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter, List children, boolean theIncludedResource) - throws XMLStreamException, DataFormatException { + private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter, + List children, boolean theIncludedResource) throws XMLStreamException, DataFormatException { for (BaseRuntimeChildDefinition nextChild : children) { if (nextChild instanceof RuntimeChildNarrativeDefinition && !theIncludedResource) { INarrativeGenerator gen = myContext.getNarrativeGenerator(); @@ -475,13 +494,13 @@ private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinit if (childDef == null) { super.throwExceptionForUnknownChildType(nextChild, type); } - + if (nextValue instanceof ExtensionDt) { - + extensionUrl = ((ExtensionDt) nextValue).getUrlAsString(); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theIncludedResource); - - } else if (extensionUrl != null && childName.equals("extension") == false) { + + } else if (extensionUrl != null && childName.equals("extension") == false) { RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild; if (extDef.isModifier()) { theEventWriter.writeStartElement("modifierExtension"); @@ -499,14 +518,15 @@ private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinit } } - private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, - DataFormatException { + private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter, + BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException { encodeExtensionsIfPresent(theResDef, theResource, theEventWriter, theElement, theIncludedResource); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIncludedResource); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren(), theIncludedResource); } - private void encodeExtensionsIfPresent(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, IElement theElement, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeExtensionsIfPresent(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, IElement theElement, boolean theIncludedResource) + throws XMLStreamException, DataFormatException { if (theElement instanceof ISupportsUndeclaredExtensions) { ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; encodeUndeclaredExtensions(theResDef, theResource, theWriter, res.getUndeclaredExtensions(), "extension", theIncludedResource); @@ -516,11 +536,11 @@ private void encodeExtensionsIfPresent(RuntimeResourceDefinition theResDef, IRes private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, ResourceReferenceDt theRef) throws XMLStreamException { String reference = theRef.getReference().getValue(); -// if (StringUtils.isBlank(reference)) { -// if (theRef.getResourceType() != null && StringUtils.isNotBlank(theRef.getResourceId())) { -// reference = myContext.getResourceDefinition(theRef.getResourceType()).getName() + '/' + theRef.getResourceId(); -// } -// } + // if (StringUtils.isBlank(reference)) { + // if (theRef.getResourceType() != null && StringUtils.isNotBlank(theRef.getResourceId())) { + // reference = myContext.getResourceDefinition(theRef.getResourceType()).getName() + '/' + theRef.getResourceId(); + // } + // } if (!(theRef.getDisplay().isEmpty())) { theEventWriter.writeStartElement("display"); @@ -536,9 +556,7 @@ private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWrite /** * @param theIncludedResource - * Set to true only if this resource is an "included" resource, - * as opposed to a "root level" resource by itself or in a bundle - * entry + * Set to true only if this resource is an "included" resource, as opposed to a "root level" resource by itself or in a bundle entry * */ private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException { @@ -569,7 +587,8 @@ private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWri theEventWriter.writeEndElement(); } - private void encodeUndeclaredExtensions(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, List extensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeUndeclaredExtensions(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, List extensions, String tagName, + boolean theIncludedResource) throws XMLStreamException, DataFormatException { for (ExtensionDt next : extensions) { theWriter.writeStartElement(tagName); theWriter.writeAttribute("url", next.getUrl().getValue()); @@ -654,7 +673,7 @@ private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws X } else { if (StringUtils.isBlank(se.getName().getPrefix())) { theEventWriter.writeStartElement(se.getName().getLocalPart()); -// theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI()); + // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI()); } else { theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart()); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index 9139d12fc9a0..51272107c41b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -63,7 +63,7 @@ public abstract class BaseClient { private boolean myKeepResponses = false; private HttpResponse myLastResponse; private String myLastResponseBody; - private boolean myPrettyPrint = false; + private Boolean myPrettyPrint = false; private final String myUrlBase; @@ -271,9 +271,17 @@ public boolean isKeepResponses() { * HAPI based servers (and any other servers which might implement it). */ public boolean isPrettyPrint() { - return myPrettyPrint; + return Boolean.TRUE.equals(myPrettyPrint); } + /** + * Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note that this is currently a non-standard flag (_pretty) which is supported only by + * HAPI based servers (and any other servers which might implement it). + */ + public Boolean getPrettyPrint() { + return myPrettyPrint; + } + private void keepResponseAndLogIt(boolean theLogRequestAndResponse, HttpResponse response, String responseString) { if (myKeepResponses) { myLastResponse = response; @@ -330,7 +338,7 @@ public void setLastResponseBody(String theLastResponseBody) { * Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note that this is currently a non-standard flag (_pretty) which is supported only by * HAPI based servers (and any other servers which might implement it). */ - public BaseClient setPrettyPrint(boolean thePrettyPrint) { + public BaseClient setPrettyPrint(Boolean thePrettyPrint) { myPrettyPrint = thePrettyPrint; return this; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 066873595cc2..17eb13250e46 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -101,7 +101,7 @@ public Conformance conformance() { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); } - ResourceResponseHandler binding = new ResourceResponseHandler(Conformance.class); + ResourceResponseHandler binding = new ResourceResponseHandler(Conformance.class, null); Conformance resp = invokeClient(binding, invocation, myLogRequestAndResponse); return resp; } @@ -181,12 +181,16 @@ public boolean isLogRequestAndResponse() { @Override public T read(final Class theType, IdDt theId) { + if (theId == null || theId.hasIdPart() == false) { + throw new IllegalArgumentException("theId does not contain a valid ID, is: " + theId); + } + HttpGetClientInvocation invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType)); if (isKeepResponses()) { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); } - ResourceResponseHandler binding = new ResourceResponseHandler(theType); + ResourceResponseHandler binding = new ResourceResponseHandler(theType, theId); T resp = invokeClient(binding, invocation, myLogRequestAndResponse); return resp; } @@ -291,7 +295,7 @@ public T vread(final Class theType, IdDt theId, IdDt th myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); } - ResourceResponseHandler binding = new ResourceResponseHandler(theType); + ResourceResponseHandler binding = new ResourceResponseHandler(theType, theId); T resp = invokeClient(binding, invocation, myLogRequestAndResponse); return resp; } @@ -594,9 +598,11 @@ public IQuery forResource(String theResourceName) { private final class ResourceResponseHandler implements IClientResponseHandler { private Class myType; + private IdDt myId; - public ResourceResponseHandler(Class theType) { + public ResourceResponseHandler(Class theType, IdDt theId) { myType = theType; + myId=theId; } @Override @@ -606,7 +612,13 @@ public T invokeClient(String theResponseMimeType, Reader theResponseReader, int throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); } IParser parser = respType.newParser(myContext); - return parser.parseResource(myType, theResponseReader); + T retVal = parser.parseResource(myType, theResponseReader); + + if (myId != null) { + retVal.setId(myId); + } + + return retVal; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java index 5edd970c78c9..342d7661ae82 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java @@ -20,6 +20,7 @@ * #L% */ +import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.server.Constants; public class StringParam implements IParam { @@ -54,6 +55,8 @@ public interface IStringMatch { ICriterion value(String theValue); + ICriterion value(StringDt theValue); + } private class StringExactly implements IStringMatch { @@ -61,6 +64,11 @@ private class StringExactly implements IStringMatch { public ICriterion value(String theValue) { return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue); } + + @Override + public ICriterion value(StringDt theValue) { + return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue.getValue()); + } } private class StringMatches implements IStringMatch { @@ -68,6 +76,11 @@ private class StringMatches implements IStringMatch { public ICriterion value(String theValue) { return new StringCriterion(getParamName(), theValue); } + + @Override + public ICriterion value(StringDt theValue) { + return new StringCriterion(getParamName(), theValue.getValue()); + } } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index b8519ba0b26b..c7515d2efed2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java @@ -177,10 +177,16 @@ public boolean incomingServerRequestMatchesMethod(Request theRequest) { } else { ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), name); return false; - } + } else { - methodParamsTemp.add(name); + if (qualifiedParamNames.contains(name)) { + methodParamsTemp.add(name); + } else if (unqualifiedNames.contains(name)) { + methodParamsTemp.addAll(theRequest.getUnqualifiedToQualifiedNames().get(name)); + } else { + methodParamsTemp.add(name); + } } } if (myQueryName != null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java index ca12a833c986..af670a821b95 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java @@ -24,6 +24,9 @@ import java.util.Collection; import java.util.List; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterAnd; @@ -57,6 +60,14 @@ public SearchParameter(String theName, boolean theRequired) { this.myRequired = theRequired; } + @Override + public String toString() { + ToStringBuilder retVal = new ToStringBuilder(this); + retVal.append("name", myName); + retVal.append("required", myRequired); + return retVal.toString(); + } + /* * (non-Javadoc) * diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/DiagnosticReport.html b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/DiagnosticReport.html index ce5bf45baf23..e224267b277c 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/DiagnosticReport.html +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/DiagnosticReport.html @@ -52,7 +52,7 @@ - + Hb 2.2 g/L diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java index 16ffbacada3b..0e0baf6f5dfb 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java @@ -12,6 +12,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.model.dstu.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.PeriodDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt; @@ -154,6 +155,11 @@ public void testGenerateDiagnosticReportWithObservations() throws DataFormatExce obs.setValue(new StringDt("HELLO!")); value.addResult().setResource(obs); } + { + Observation obs = new Observation(); + obs.setName(new CodeableConceptDt("AA", "BB")); + value.addResult().setResource(obs); + } NarrativeDt generateNarrative = gen.generateNarrative("http://hl7.org/fhir/profiles/DiagnosticReport", value); String output = generateNarrative.getDiv().getValueAsString(); @@ -161,8 +167,8 @@ public void testGenerateDiagnosticReportWithObservations() throws DataFormatExce assertThat(output, StringContains.containsString("
Some & Diagnostic Report
")); String title = gen.generateTitle(value); - ourLog.info(title); - assertEquals("Some & Diagnostic Report - final - 2 observations", title); +// ourLog.info(title); + assertEquals("Some & Diagnostic Report - final - 3 observations", title); // Now try it with the parser 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 a67f82e72ce6..c231cb778fde 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 @@ -878,7 +878,12 @@ public void testParseBundleDeletedEntry() { "" + "2014-02-10T04:11:24.435+00:00" + "" + - "" + + "" + + "John Doe" + + "jdoe@example.org" + + "" + + "Removed comment spam" + + "" + "" + ""; //@formatter:on @@ -893,6 +898,9 @@ public void testParseBundleDeletedEntry() { assertEquals("1", entry.getResource().getId().getIdPart()); assertEquals("2", entry.getResource().getId().getVersionIdPart()); assertEquals("2", ((IdDt)entry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION_ID)).getVersionIdPart()); + assertEquals("John Doe", entry.getDeletedByName().getValue()); + assertEquals("jdoe@example.org", entry.getDeletedByEmail().getValue()); + assertEquals("Removed comment spam", entry.getDeletedComment().getValue()); assertEquals(new InstantDt("2013-02-10T04:11:24.435+00:00"), entry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.DELETED_AT)); ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(bundle)); diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index c923a894f538..b1849b083a4c 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -32,6 +32,7 @@ import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.server.Constants; @@ -115,6 +116,23 @@ private String getPatientFeedWithOneResult() { return msg; } + private String getResourceResult() { + //@formatter:off + String msg = + "" + + "
John Cardinal: 444333333
" + + "" + + "" + + "" + + "" + + "" + + "
" + + "
"; + //@formatter:on + return msg; + } + + @SuppressWarnings("unused") @Test public void testSearchByString() throws Exception { @@ -406,6 +424,32 @@ public void testSearchByDate() throws Exception { assertEquals("http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json", capt.getValue().getURI().toString()); } + + + + + @Test + public void testRead() throws Exception { + + String msg = getResourceResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Patient response = client.read(Patient.class, new IdDt("Patient/1234")); + //@formatter:on + + assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal")); + assertEquals("Patient/1234", response.getId().getValue()); + + } + @SuppressWarnings("unused") @Test diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/StringParameterTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/StringParameterTest.java index 872b2ea34023..d9f16c11d57e 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/StringParameterTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/StringParameterTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.List; @@ -22,6 +22,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.param.StringParam; @@ -127,6 +128,19 @@ public void testSearchExactMatch() throws Exception { } } + @Test + public void testSearchExactMatchOptional() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?ccc:exact=aaa"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); + + assertEquals(200, status.getStatusLine().getStatusCode()); + assertEquals(1, new FhirContext().newXmlParser().parseBundle(responseContent).getEntries().size()); + } + } + + @AfterClass public static void afterClass() throws Exception { ourServer.stop(); @@ -190,6 +204,25 @@ public List findPatient(@RequiredParam(name = "plain") String theParam) return retVal; } + @Search + public List findPatientWithOptional(@OptionalParam(name = "ccc") StringParam theParam) { + ArrayList retVal = new ArrayList(); + + if (theParam.isExact() && theParam.getValue().equals("aaa")) { + Patient patient = new Patient(); + patient.setId("1"); + retVal.add(patient); + } + if (!theParam.isExact() && theParam.getValue().toLowerCase().equals("aaa")) { + Patient patient = new Patient(); + patient.setId("2"); + retVal.add(patient); + } + + return retVal; + } + + @Override public Class getResourceType() { return Patient.class; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml index 308e9a37a5bf..a479f40f9696 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml @@ -17,7 +17,7 @@ furore , Spark - Furore Reference Server , http://spark.furore.com/fhir blaze , Blaze (Orion Health) , https://fhir.orionhealth.com/blaze/fhir oridashi , Oridashi , http://demo.oridashi.com.au:8190 - fhirbase , FHIRPlace (Health Samurai) , http://try-fhirplace.hospital-systems.com/ + diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java index 127d39325307..fe37b3226532 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java @@ -82,9 +82,15 @@ public String actionAbout(final HomeRequest theRequest, final ModelMap theModel) theModel.put("notHome", true); theModel.put("extraBreadcrumb", "About"); + ourLog.info(logPrefix(theModel) + "Displayed about page"); + return "about"; } + private String logPrefix(ModelMap theModel) { + return "[server=" + theModel.get("serverId") + "] - "; + } + @RequestMapping(value = { "/conformance" }) public String actionConformance(final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) { addCommonParams(theRequest, theModel); @@ -102,6 +108,8 @@ public String actionConformance(final HomeRequest theRequest, final BindingResul processAndAddLastClientInvocation(client, returnsResource, theModel, delay, "Loaded conformance"); + ourLog.info(logPrefix(theModel) + "Displayed conformance profile"); + return "result"; } @@ -143,6 +151,8 @@ public String actionDelete(HttpServletRequest theReq, HomeRequest theRequest, Bi long delay = System.currentTimeMillis() - start; processAndAddLastClientInvocation(client, returnsResource, theModel, delay, outcomeDescription); + ourLog.info(logPrefix(theModel) + "Deleted resource of type " + def.getName()); + return "result"; } @@ -185,14 +195,18 @@ public String actionGetTags(HttpServletRequest theReq, HomeRequest theRequest, B String vid = theReq.getParameter("resource-tags-vid"); if (isNotBlank(vid)) { client.getTags().forResource(resType, id, vid).execute(); + ourLog.info(logPrefix(theModel) + "Got tags for type " + def.getName() + " ID " + id + " version" + vid); } else { client.getTags().forResource(resType, id).execute(); + ourLog.info(logPrefix(theModel) + "Got tags for type " + def.getName() + " ID " + id); } } else { client.getTags().forResource(resType).execute(); + ourLog.info(logPrefix(theModel) + "Got tags for type " + def.getName()); } } else { client.getTags().execute(); + ourLog.info(logPrefix(theModel) + "Got tags for server"); } } catch (Exception e) { returnsResource = handleClientException(client, e, theModel); @@ -230,6 +244,7 @@ public String actionPage(HttpServletRequest theReq, HomeRequest theRequest, Bind String url = defaultString(theReq.getParameter("page-url")); if (!url.startsWith(theModel.get("base").toString())) { + ourLog.warn(logPrefix(theModel) + "Refusing to load page URL: {}", url); theModel.put("errorMsg", "Invalid page URL: " + url); return "result"; } @@ -240,6 +255,7 @@ public String actionPage(HttpServletRequest theReq, HomeRequest theRequest, Bind long start = System.currentTimeMillis(); try { + ourLog.info(logPrefix(theModel) + "Loading paging URL: {}", url); client.loadPage().url(url).execute(); } catch (Exception e) { returnsResource = handleClientException(client, e, theModel); @@ -284,7 +300,9 @@ public String actionRead(HttpServletRequest theReq, HomeRequest theRequest, Bind long start = System.currentTimeMillis(); try { - client.read(def.getImplementingClass(), new IdDt(def.getName(), id, versionId)); + IdDt resid = new IdDt(def.getName(), id, versionId); + ourLog.info(logPrefix(theModel) + "Reading resource: {}", resid); + client.read(def.getImplementingClass(), resid); } catch (Exception e) { returnsResource = handleClientException(client, e, theModel); } @@ -360,6 +378,8 @@ public String actionResource(final ResourceRequest theRequest, final BindingResu theModel.put("updateResourceId", updateId); } + ourLog.info(logPrefix(theModel) + "Showing resource page: {}", resourceName); + return "resource"; } @@ -371,8 +391,8 @@ public String actionSearch(HttpServletRequest theReq, HomeRequest theRequest, Bi JsonGenerator clientCodeJsonWriter = Json.createGenerator(clientCodeJsonStringWriter); clientCodeJsonWriter.writeStartObject(); clientCodeJsonWriter.write("action", "search"); - clientCodeJsonWriter.write("base", (String)theModel.get("base")); - + clientCodeJsonWriter.write("base", (String) theModel.get("base")); + GenericClient client = theRequest.newClient(myCtx, myConfig); IUntypedQuery search = client.search(); @@ -389,29 +409,45 @@ public String actionSearch(HttpServletRequest theReq, HomeRequest theRequest, Bi query = search.forAllResources(); clientCodeJsonWriter.writeNull("resource"); } - + + if (client.getPrettyPrint() != null) { + clientCodeJsonWriter.write("pretty", client.getPrettyPrint().toString()); + } else { + clientCodeJsonWriter.writeNull("pretty"); + } + + if (client.getEncoding() != null) { + clientCodeJsonWriter.write("format", client.getEncoding().getRequestContentType()); + } else { + clientCodeJsonWriter.writeNull("format"); + } String outcomeDescription = "Search for Resources"; + clientCodeJsonWriter.writeStartArray("params"); int paramIdx = -1; while (true) { paramIdx++; String paramIdxString = Integer.toString(paramIdx); - boolean shouldContinue = handleSearchParam(paramIdxString, theReq, query); + boolean shouldContinue = handleSearchParam(paramIdxString, theReq, query, clientCodeJsonWriter); if (!shouldContinue) { break; } } + clientCodeJsonWriter.writeEnd(); + clientCodeJsonWriter.writeStartArray("includes"); String[] incValues = theReq.getParameterValues(Constants.PARAM_INCLUDE); if (incValues != null) { for (String next : incValues) { if (isNotBlank(next)) { query.include(new Include(next)); + clientCodeJsonWriter.write(next); } } } + clientCodeJsonWriter.writeEnd(); String limit = theReq.getParameter("resource-search-limit"); if (isNotBlank(limit)) { @@ -419,12 +455,18 @@ public String actionSearch(HttpServletRequest theReq, HomeRequest theRequest, Bi theModel.put("errorMsg", "Search limit must be a numeric value."); return "resource"; } - query.limitTo(Integer.parseInt(limit)); + int limitInt = Integer.parseInt(limit); + query.limitTo(limitInt); + clientCodeJsonWriter.write("limit", limit); + } else { + clientCodeJsonWriter.writeNull("limit"); } long start = System.currentTimeMillis(); ResultType returnsResource; try { + ourLog.info(logPrefix(theModel) + "Executing a search"); + query.execute(); returnsResource = ResultType.BUNDLE; } catch (Exception e) { @@ -438,7 +480,7 @@ public String actionSearch(HttpServletRequest theReq, HomeRequest theRequest, Bi clientCodeJsonWriter.close(); String clientCodeJson = clientCodeJsonStringWriter.toString(); theModel.put("clientCodeJson", clientCodeJson); - + return "result"; } @@ -466,11 +508,17 @@ public String actionTransaction(final TransactionRequest theRequest, final Bindi return "home"; } + ResultType returnsResource = ResultType.BUNDLE; long start = System.currentTimeMillis(); - client.transaction().withBundle(bundle).execute(); + try { + ourLog.info(logPrefix(theModel) + "Executing transaction with {} resources", bundle.size()); + client.transaction().withBundle(bundle).execute(); + } catch (Exception e) { + returnsResource = handleClientException(client, e, theModel); + } long delay = System.currentTimeMillis() - start; - processAndAddLastClientInvocation(client, ResultType.BUNDLE, theModel, delay, "Transaction"); + processAndAddLastClientInvocation(client, returnsResource, theModel, delay, "Transaction"); return "result"; } @@ -555,6 +603,7 @@ private void doActionCreateOrValidate(HttpServletRequest theReq, HomeRequest the long start = System.currentTimeMillis(); ResultType returnsResource = ResultType.RESOURCE; outcomeDescription = ""; + boolean update = false; try { if (validate) { outcomeDescription = "Validate Resource"; @@ -564,6 +613,7 @@ private void doActionCreateOrValidate(HttpServletRequest theReq, HomeRequest the if (isNotBlank(id)) { outcomeDescription = "Update Resource"; client.update(id, resource); + update = true; } else { outcomeDescription = "Create Resource"; client.create(resource); @@ -576,6 +626,18 @@ private void doActionCreateOrValidate(HttpServletRequest theReq, HomeRequest the processAndAddLastClientInvocation(client, returnsResource, theModel, delay, outcomeDescription); + try { + if (validate) { + ourLog.info(logPrefix(theModel) + "Validated resource of type " + getResourceType(theReq).getName()); + } else if (update) { + ourLog.info(logPrefix(theModel) + "Updated resource of type " + getResourceType(theReq).getName()); + } else { + ourLog.info(logPrefix(theModel) + "Created resource of type " + getResourceType(theReq).getName()); + } + } catch (Exception e) { + ourLog.warn("Failed to determine resource type from request", e); + } + } private void doActionHistory(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel, String theMethod, String theMethodDescription) { @@ -603,11 +665,18 @@ private void doActionHistory(HttpServletRequest theReq, HomeRequest theRequest, limit = Integer.parseInt(limitStr); } + ResultType returnsResource = ResultType.BUNDLE; + long start = System.currentTimeMillis(); - client.history(type, id, since, limit); + try { + ourLog.info(logPrefix(theModel) + "Retrieving history for type {} ID {} since {}", new Object[] { type, id, since }); + client.history(type, id, since, limit); + } catch (Exception e) { + returnsResource = handleClientException(client, e, theModel); + } long delay = System.currentTimeMillis() - start; - processAndAddLastClientInvocation(client, ResultType.BUNDLE, theModel, delay, theMethodDescription); + processAndAddLastClientInvocation(client, returnsResource, theModel, delay, theMethodDescription); } @@ -770,42 +839,59 @@ private RuntimeResourceDefinition getResourceType(HttpServletRequest theReq) thr return def; } - private boolean handleSearchParam(String paramIdxString, HttpServletRequest theReq, IQuery theQuery) { + private boolean handleSearchParam(String paramIdxString, HttpServletRequest theReq, IQuery theQuery, JsonGenerator theClientCodeJsonWriter) { String nextName = theReq.getParameter("param." + paramIdxString + ".name"); if (isBlank(nextName)) { return false; } String nextQualifier = StringUtils.defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier")); - String nextType = theReq.getParameter("param." + paramIdxString + ".type"); - StringBuilder b = new StringBuilder(); - for (int i = 0; i < 100; i++) { - b.append(defaultString(theReq.getParameter("param." + paramIdxString + "." + i))); - } - - String paramValue = b.toString(); - if (isBlank(paramValue)) { - return true; + List parts = new ArrayList(); + for (int i = 0; i < 5; i++) { + parts.add(defaultString(theReq.getParameter("param." + paramIdxString + "." + i))); } + List values; if ("token".equals(nextType)) { - if (paramValue.length() < 2) { + if (isBlank(parts.get(2))) { + return true; + } + values = Collections.singletonList(StringUtils.join(parts, "")); + } else if ("date".equals(nextType)) { + values = new ArrayList(); + if (isNotBlank(parts.get(1))) { + values.add(StringUtils.join(parts.get(0), parts.get(1))); + } + if (isNotBlank(parts.get(3))) { + values.add(StringUtils.join(parts.get(2), parts.get(3))); + } + if (values.isEmpty()) { + return true; + } + } else { + values = Collections.singletonList(StringUtils.join(parts, "")); + if (isBlank(values.get(0))) { return true; } } - // if ("xml".equals(theReq.getParameter("encoding"))) { - // query.encodedXml(); - // }else if ("json".equals(theReq.getParameter("encoding"))) { - // query.encodedJson(); - // } + for (String nextValue : values) { - theQuery.where(new StringParam(nextName + nextQualifier).matches().value(paramValue)); + theClientCodeJsonWriter.writeStartObject(); + theClientCodeJsonWriter.write("type", nextType); + theClientCodeJsonWriter.write("name", nextName); + theClientCodeJsonWriter.write("qualifier", nextQualifier); + theClientCodeJsonWriter.write("value", nextValue); + theClientCodeJsonWriter.writeEnd(); + + theQuery.where(new StringParam(nextName + nextQualifier).matches().value(nextValue)); + + } if (StringUtils.isNotBlank(theReq.getParameter("param." + paramIdxString + ".0.name"))) { - handleSearchParam(paramIdxString + ".0", theReq, theQuery); + handleSearchParam(paramIdxString + ".0", theReq, theQuery, theClientCodeJsonWriter); } return true; diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml index 92c597355034..4dbfdc97a3b7 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml @@ -15,8 +15,9 @@ home , Localhost Server , http://localhost:8887/fhir/context hi , Health Intersections , http://fhir.healthintersections.com.au/open furore , Spark - Furore Reference Server , http://spark.furore.com/fhir - blaze , Blaze (Orion Health) , https://his-medicomp-gateway.orionhealth.com/blaze/fhir + blaze , Blaze (Orion Health) , https://fhir.orionhealth.com/blaze/fhir oridashi , Oridashi , http://demo.oridashi.com.au:8190 + fhirbase , FHIRPlace (Health Samurai) , http://try-fhirplace.hospital-systems.com/ diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html index f5398ec3c847..221ab06c529a 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html @@ -537,5 +537,11 @@

Other Options

+ + diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/result.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/result.html index 75f5bb344ae5..b69233af3893 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/result.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/result.html @@ -27,7 +27,6 @@ -
diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-queries.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-queries.html index e4052699fb28..f66c2f2365d4 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-queries.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-queries.html @@ -44,6 +44,7 @@

+ diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/css/tester.css b/hapi-fhir-testpage-overlay/src/main/webapp/css/tester.css index 08b3b645c286..dbf5e91f8900 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/css/tester.css +++ b/hapi-fhir-testpage-overlay/src/main/webapp/css/tester.css @@ -22,11 +22,20 @@ body { line-height: 0.85em; } +.clientCodeComment { + color: #4A4; + font-style: italic; +} + .clientCodePreamble { color: #888; font-style: italic; } +.clientCodeIndent { + margin-left: 15px; +} + label { font-size: 1.0em; color: #808080; diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js b/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js index afef1af480ae..fbc639aa6f08 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js +++ b/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js @@ -1,4 +1,4 @@ - + function generateHapi(json, container) { if (json.action == 'search') { generateHapiSearch(json, container); @@ -6,12 +6,17 @@ function generateHapi(json, container) { } function generateHapiSearch(json, container) { + container.append($('', {'class': 'clientCodeComment'}).text("// Create a client (only needed once)")); + container.append($('
')); container.append($('', {'class': 'clientCodePreamble'}).text("FhirContext ctx = new FhirContext();")); container.append($('
')); container.append($('', {'class': 'clientCodePreamble'}).text("IGenericClient client = ctx.newRestfulGenericClient(\"" + json.base + "\");")); container.append($('
')); + container.append($('
')); + container.append($('', {'class': 'clientCodeComment'}).text("// Invoke the client")); + container.append($('
')); - var searchLine = 'client.search()'; + var searchLine = 'Bundle bundle = client.search()'; if (json.resource != null) { searchLine = searchLine + '.forResource(' + json.resource + '.class)'; } else { @@ -19,4 +24,60 @@ function generateHapiSearch(json, container) { } container.append($('', {'class': 'clientCodeMain'}).text(searchLine)); + var indented = $('
', {'class': 'clientCodeIndent'}); + container.append(indented); + + if (json.pretty) { + indented.append($('', {'class': 'clientCodeMain'}).text('.setPrettyPrint(' + json.pretty + ')')); + indented.append($('
')); + } + + if (json.format) { + indented.append($('', {'class': 'clientCodeMain'}).text('.setEncoding(EncodingEnum.' + json.format.toUpperCase() + ')')); + indented.append($('
')); + } + + for (var i = 0; i < json.params.length; i++) { + var nextParam = json.params[i]; + var paramLine = null; + if (nextParam.type == 'string') { + paramLine = '.where(new StringParam("' + nextParam.name + '").matches().value("' + nextParam.value + '"))'; + } else if (nextParam.type == 'token') { + var idx = nextParam.value.indexOf('|'); + if (idx == -1) { + paramLine = '.where(new TokenParam("' + nextParam.name + '").exactly().code("' + nextParam.value + '"))'; + } else { + paramLine = '.where(new TokenParam("' + nextParam.name + '").exactly().systemAndCode("' + nextParam.value.substring(0,idx) + '", "' + nextParam.value.substring(idx+1) + '"))'; + } + } else if (nextParam.type == 'number') { + paramLine = '.where(new NumberParam("' + nextParam.name + '").exactly().value("' + nextParam.value + '"))'; + } else if (nextParam.type == 'date') { + if (nextParam.value.substring(0,2) == '>=') { + paramLine = '.where(new DateParam("' + nextParam.name + '").afterOrEquals().value("' + nextParam.value.substring(2) + '"))'; + } else if (nextParam.value.substring(0,1) == '>') { + paramLine = '.where(new DateParam("' + nextParam.name + '").after().value("' + nextParam.value.substring(1) + '"))'; + } else if (nextParam.value.substring(0,2) == '<=') { + paramLine = '.where(new DateParam("' + nextParam.name + '").beforeOrEquals().value("' + nextParam.value.substring(2) + '"))'; + } else if (nextParam.value.substring(0,1) == '<') { + paramLine = '.where(new DateParam("' + nextParam.name + '").before().value("' + nextParam.value.substring(1) + '"))'; + } + } + if (paramLine != null) { + indented.append($('', {'class': 'clientCodeMain'}).text(paramLine)); + indented.append($('
')); + } + } + + for (var i = 0; i < json.includes.length; i++) { + indented.append($('', {'class': 'clientCodeMain'}).text('.include(new Include("' + json.includes[i] + '"))')); + indented.append($('
')); + } + + if (json.limit) { + indented.append($('', {'class': 'clientCodeMain'}).text('.limitTo(' + json.limit + ')')); + indented.append($('
')); + } + + indented.append($('', {'class': 'clientCodeMain'}).text('.execute();')); + } diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js b/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js index d50723d5613a..3fb160280dce 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js +++ b/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js @@ -95,55 +95,70 @@ function addSearchControls(theSearchParamType, theSearchParamName, theSearchPara ) ); } else if (theSearchParamType == 'date') { - var qualifier = $('', {type:'hidden', id:'param.'+theRowNum+'.0', id:'param.'+theRowNum+'.0'}); - - if (/date$/.test(theSearchParamName)) { - var input = $('
', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DD' }); - } else { - var input = $('
', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DDTHH:mm:ss' }); - } - var qualifierDiv = $('
'); - input.append( - qualifierDiv, - $('', { type:'text', 'class':'form-control', id: 'param.' + theRowNum + '.1' }), - $('
', { 'class':'input-group-addon', 'style':'padding:6px;'} ).append( - $('', { 'class':'fa fa-chevron-circle-down'}) - ) - ); - input.datetimepicker({ - pickTime: false, - showToday: true - }); - // Set up the qualifier dropdown after we've initialized the datepicker, since it - // overrides all addon buttons while it inits.. - qualifierDiv.addClass('input-group-btn'); - var qualifierBtn = $(''); - var qualifierBtnEq = $('=').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '='); }); - var qualifierBtnGt = $('>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '>'); }); - var qualifierBtnGe = $('>=').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '>='); }); - var qualifierBtnLt = $('<').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '<'); }); - var qualifierBtnLe = $('<=').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '<='); }); - qualifierDiv.append( - qualifierBtn, - $('