More work on tester

This commit is contained in:
James Agnew 2014-07-11 18:27:27 -04:00
parent 0a7f7d0390
commit a2a1035003
25 changed files with 573 additions and 143 deletions

View File

@ -71,6 +71,15 @@
<action type="fix"> <action type="fix">
Server now automatically compresses responses if the client indicates support Server now automatically compresses responses if the client indicates support
</action> </action>
<action type="fix">
Server failed to support optional parameters when type is String and :exact qualifier is used
</action>
<action type="fix">
Read method in client correctly populated resource ID in returned object
</action>
<action type="add">
Support added for deleted-entry by/name, by/email, and comment from Tombstones spec
</action>
</release> </release>
</body> </body>
</document> </document>

View File

@ -58,13 +58,12 @@ public class Bundle extends BaseBundle /* implements IElement */{
private IntegerDt myTotalResults; private IntegerDt myTotalResults;
private InstantDt myUpdated; private InstantDt myUpdated;
/**
* Returns true if this bundle contains zero entries
*/
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
//@formatter:off return getEntries().isEmpty();
return super.isEmpty() &&
ElementUtil.isEmpty(myBundleId, myLinkBase, myLinkFirst, myLinkLast, myLinkNext, myLinkPrevious, myLinkSelf, myPublished, myTitle, myTotalResults) &&
ElementUtil.isEmpty(myEntries);
//@formatter:on
} }
/** /**

View File

@ -38,6 +38,9 @@ public class BundleEntry extends BaseBundle {
//@formatter:on //@formatter:on
private TagList myCategories; private TagList myCategories;
private InstantDt myDeletedAt; private InstantDt myDeletedAt;
private StringDt myDeletedByEmail;
private StringDt myDeletedByName;
private StringDt myDeletedComment;
private StringDt myLinkAlternate; private StringDt myLinkAlternate;
private StringDt myLinkSelf; private StringDt myLinkSelf;
private InstantDt myPublished; private InstantDt myPublished;
@ -52,18 +55,6 @@ public class BundleEntry extends BaseBundle {
return retVal; 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) { public void addCategory(Tag theTag) {
getCategories().add(theTag); getCategories().add(theTag);
} }
@ -85,6 +76,27 @@ public class BundleEntry extends BaseBundle {
return myDeletedAt; 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() { public StringDt getLinkAlternate() {
if (myLinkAlternate == null) { if (myLinkAlternate == null) {
myLinkAlternate = new StringDt(); myLinkAlternate = new StringDt();
@ -135,7 +147,7 @@ public class BundleEntry extends BaseBundle {
public boolean isEmpty() { public boolean isEmpty() {
//@formatter:off //@formatter:off
return super.isEmpty() && 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 //@formatter:on
} }
@ -146,6 +158,21 @@ public class BundleEntry extends BaseBundle {
myDeletedAt = 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) { public void setLinkAlternate(StringDt theLinkAlternate) {
myLinkAlternate = theLinkAlternate; myLinkAlternate = theLinkAlternate;
} }
@ -171,4 +198,16 @@ public class BundleEntry extends BaseBundle {
myUpdated = 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();
}
} }

View File

@ -525,10 +525,14 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue); final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue);
final Object value = expression.execute(configuration, theArguments); final Object value = expression.execute(configuration, theArguments);
theElement.removeAttribute(theAttributeName); theElement.removeAttribute(theAttributeName);
theElement.clearChildren(); theElement.clearChildren();
if (value == null) {
return ProcessorResult.ok();
}
Context context = new Context(); Context context = new Context();
context.setVariable("resource", value); context.setVariable("resource", value);

View File

@ -157,8 +157,7 @@ class ParserState<T> {
} }
/** /**
* Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically * Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically intended for embedded XHTML content
* intended for embedded XHTML content
*/ */
public void xmlEvent(XMLEvent theNextEvent) { public void xmlEvent(XMLEvent theNextEvent) {
myState.xmlEvent(theNextEvent); myState.xmlEvent(theNextEvent);
@ -238,8 +237,7 @@ class ParserState<T> {
myInstance.setScheme(theValue); myInstance.setScheme(theValue);
} else if ("value".equals(theName)) { } else if ("value".equals(theName)) {
/* /*
* This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values * This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values instead of one like everything else.
* instead of one like everything else.
*/ */
switch (myCatState) { switch (myCatState) {
case STATE_LABEL: case STATE_LABEL:
@ -299,6 +297,23 @@ class ParserState<T> {
} }
} }
@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 @Override
public void endingElement() throws DataFormatException { public void endingElement() throws DataFormatException {
putPlacerResourceInDeletedEntry(getEntry()); putPlacerResourceInDeletedEntry(getEntry());
@ -307,6 +322,33 @@ class ParserState<T> {
} }
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 class AtomDeletedJsonWhenState extends BaseState {
private String myData; private String myData;

View File

@ -143,13 +143,32 @@ public class XmlParser extends BaseParser implements IParser {
} }
for (BundleEntry nextEntry : theBundle.getEntries()) { for (BundleEntry nextEntry : theBundle.getEntries()) {
boolean deleted=false; boolean deleted = false;
if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty()==false) { if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) {
deleted=true; deleted = true;
eventWriter.writeStartElement("at","deleted-entry",TOMBSTONES_NS); eventWriter.writeStartElement("at", "deleted-entry", TOMBSTONES_NS);
eventWriter.writeNamespace("at", TOMBSTONES_NS); eventWriter.writeNamespace("at", TOMBSTONES_NS);
eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString()); eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString());
eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString()); eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString());
if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty()) {
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 { } else {
eventWriter.writeStartElement("entry"); eventWriter.writeStartElement("entry");
} }
@ -377,8 +396,8 @@ public class XmlParser extends BaseParser implements IParser {
} }
} }
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName, BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl, boolean theIncludedResource) private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName,
throws XMLStreamException, DataFormatException { BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
if (nextValue.isEmpty()) { if (nextValue.isEmpty()) {
return; return;
} }
@ -442,8 +461,8 @@ public class XmlParser extends BaseParser implements IParser {
} }
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter, List<? extends BaseRuntimeChildDefinition> children, boolean theIncludedResource) private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter,
throws XMLStreamException, DataFormatException { List<? extends BaseRuntimeChildDefinition> children, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
for (BaseRuntimeChildDefinition nextChild : children) { for (BaseRuntimeChildDefinition nextChild : children) {
if (nextChild instanceof RuntimeChildNarrativeDefinition && !theIncludedResource) { if (nextChild instanceof RuntimeChildNarrativeDefinition && !theIncludedResource) {
INarrativeGenerator gen = myContext.getNarrativeGenerator(); INarrativeGenerator gen = myContext.getNarrativeGenerator();
@ -475,13 +494,13 @@ public class XmlParser extends BaseParser implements IParser {
if (childDef == null) { if (childDef == null) {
super.throwExceptionForUnknownChildType(nextChild, type); super.throwExceptionForUnknownChildType(nextChild, type);
} }
if (nextValue instanceof ExtensionDt) { if (nextValue instanceof ExtensionDt) {
extensionUrl = ((ExtensionDt) nextValue).getUrlAsString(); extensionUrl = ((ExtensionDt) nextValue).getUrlAsString();
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theIncludedResource); 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; RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild;
if (extDef.isModifier()) { if (extDef.isModifier()) {
theEventWriter.writeStartElement("modifierExtension"); theEventWriter.writeStartElement("modifierExtension");
@ -499,14 +518,15 @@ public class XmlParser extends BaseParser implements IParser {
} }
} }
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef, boolean theIncludedResource) throws XMLStreamException, private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter,
DataFormatException { BaseRuntimeElementCompositeDefinition<?> resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
encodeExtensionsIfPresent(theResDef, theResource, theEventWriter, theElement, theIncludedResource); encodeExtensionsIfPresent(theResDef, theResource, theEventWriter, theElement, theIncludedResource);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIncludedResource); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIncludedResource);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren(), 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) { if (theElement instanceof ISupportsUndeclaredExtensions) {
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
encodeUndeclaredExtensions(theResDef, theResource, theWriter, res.getUndeclaredExtensions(), "extension", theIncludedResource); encodeUndeclaredExtensions(theResDef, theResource, theWriter, res.getUndeclaredExtensions(), "extension", theIncludedResource);
@ -516,11 +536,11 @@ public class XmlParser extends BaseParser implements IParser {
private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, ResourceReferenceDt theRef) throws XMLStreamException { private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, ResourceReferenceDt theRef) throws XMLStreamException {
String reference = theRef.getReference().getValue(); String reference = theRef.getReference().getValue();
// if (StringUtils.isBlank(reference)) { // if (StringUtils.isBlank(reference)) {
// if (theRef.getResourceType() != null && StringUtils.isNotBlank(theRef.getResourceId())) { // if (theRef.getResourceType() != null && StringUtils.isNotBlank(theRef.getResourceId())) {
// reference = myContext.getResourceDefinition(theRef.getResourceType()).getName() + '/' + theRef.getResourceId(); // reference = myContext.getResourceDefinition(theRef.getResourceType()).getName() + '/' + theRef.getResourceId();
// } // }
// } // }
if (!(theRef.getDisplay().isEmpty())) { if (!(theRef.getDisplay().isEmpty())) {
theEventWriter.writeStartElement("display"); theEventWriter.writeStartElement("display");
@ -536,9 +556,7 @@ public class XmlParser extends BaseParser implements IParser {
/** /**
* @param theIncludedResource * @param theIncludedResource
* Set to true only if this resource is an "included" resource, * 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
* as opposed to a "root level" resource by itself or in a bundle
* entry
* *
*/ */
private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException { private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
@ -569,7 +587,8 @@ public class XmlParser extends BaseParser implements IParser {
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
private void encodeUndeclaredExtensions(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, List<ExtensionDt> extensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException { private void encodeUndeclaredExtensions(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, List<ExtensionDt> extensions, String tagName,
boolean theIncludedResource) throws XMLStreamException, DataFormatException {
for (ExtensionDt next : extensions) { for (ExtensionDt next : extensions) {
theWriter.writeStartElement(tagName); theWriter.writeStartElement(tagName);
theWriter.writeAttribute("url", next.getUrl().getValue()); theWriter.writeAttribute("url", next.getUrl().getValue());
@ -654,7 +673,7 @@ public class XmlParser extends BaseParser implements IParser {
} else { } else {
if (StringUtils.isBlank(se.getName().getPrefix())) { if (StringUtils.isBlank(se.getName().getPrefix())) {
theEventWriter.writeStartElement(se.getName().getLocalPart()); theEventWriter.writeStartElement(se.getName().getLocalPart());
// theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI()); // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
} else { } else {
theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart()); theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart());
} }

View File

@ -63,7 +63,7 @@ public abstract class BaseClient {
private boolean myKeepResponses = false; private boolean myKeepResponses = false;
private HttpResponse myLastResponse; private HttpResponse myLastResponse;
private String myLastResponseBody; private String myLastResponseBody;
private boolean myPrettyPrint = false; private Boolean myPrettyPrint = false;
private final String myUrlBase; private final String myUrlBase;
@ -271,9 +271,17 @@ public abstract class BaseClient {
* HAPI based servers (and any other servers which might implement it). * HAPI based servers (and any other servers which might implement it).
*/ */
public boolean isPrettyPrint() { 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) { private void keepResponseAndLogIt(boolean theLogRequestAndResponse, HttpResponse response, String responseString) {
if (myKeepResponses) { if (myKeepResponses) {
myLastResponse = response; myLastResponse = response;
@ -330,7 +338,7 @@ public abstract class BaseClient {
* 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 * 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). * HAPI based servers (and any other servers which might implement it).
*/ */
public BaseClient setPrettyPrint(boolean thePrettyPrint) { public BaseClient setPrettyPrint(Boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint; myPrettyPrint = thePrettyPrint;
return this; return this;
} }

View File

@ -101,7 +101,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
ResourceResponseHandler<Conformance> binding = new ResourceResponseHandler<Conformance>(Conformance.class); ResourceResponseHandler<Conformance> binding = new ResourceResponseHandler<Conformance>(Conformance.class, null);
Conformance resp = invokeClient(binding, invocation, myLogRequestAndResponse); Conformance resp = invokeClient(binding, invocation, myLogRequestAndResponse);
return resp; return resp;
} }
@ -181,12 +181,16 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override @Override
public <T extends IResource> T read(final Class<T> theType, IdDt theId) { public <T extends IResource> T read(final Class<T> 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)); HttpGetClientInvocation invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType));
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType); ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, theId);
T resp = invokeClient(binding, invocation, myLogRequestAndResponse); T resp = invokeClient(binding, invocation, myLogRequestAndResponse);
return resp; return resp;
} }
@ -291,7 +295,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType); ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, theId);
T resp = invokeClient(binding, invocation, myLogRequestAndResponse); T resp = invokeClient(binding, invocation, myLogRequestAndResponse);
return resp; return resp;
} }
@ -594,9 +598,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class ResourceResponseHandler<T extends IResource> implements IClientResponseHandler<T> { private final class ResourceResponseHandler<T extends IResource> implements IClientResponseHandler<T> {
private Class<T> myType; private Class<T> myType;
private IdDt myId;
public ResourceResponseHandler(Class<T> theType) { public ResourceResponseHandler(Class<T> theType, IdDt theId) {
myType = theType; myType = theType;
myId=theId;
} }
@Override @Override
@ -606,7 +612,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
} }
IParser parser = respType.newParser(myContext); IParser parser = respType.newParser(myContext);
return parser.parseResource(myType, theResponseReader); T retVal = parser.parseResource(myType, theResponseReader);
if (myId != null) {
retVal.setId(myId);
}
return retVal;
} }
} }

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.gclient;
* #L% * #L%
*/ */
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
public class StringParam implements IParam { public class StringParam implements IParam {
@ -54,6 +55,8 @@ public class StringParam implements IParam {
ICriterion value(String theValue); ICriterion value(String theValue);
ICriterion value(StringDt theValue);
} }
private class StringExactly implements IStringMatch { private class StringExactly implements IStringMatch {
@ -61,6 +64,11 @@ public class StringParam implements IParam {
public ICriterion value(String theValue) { public ICriterion value(String theValue) {
return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, 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 { private class StringMatches implements IStringMatch {
@ -68,6 +76,11 @@ public class StringParam implements IParam {
public ICriterion value(String theValue) { public ICriterion value(String theValue) {
return new StringCriterion(getParamName(), theValue); return new StringCriterion(getParamName(), theValue);
} }
@Override
public ICriterion value(StringDt theValue) {
return new StringCriterion(getParamName(), theValue.getValue());
}
} }
} }

View File

@ -177,10 +177,16 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
} else { } else {
ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), name); ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), name);
return false; return false;
} }
} else { } 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) { if (myQueryName != null) {

View File

@ -24,6 +24,9 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterAnd;
@ -57,6 +60,14 @@ public class SearchParameter extends BaseQueryParameter {
this.myRequired = 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) * (non-Javadoc)
* *

View File

@ -52,7 +52,7 @@
<td> <td>
<th:block th:if="${not result.resource.name.text.empty}" th:text="${result.resource.name.text.value}"/> <th:block th:if="${not result.resource.name.text.empty}" th:text="${result.resource.name.text.value}"/>
<th:block th:if="${result.resource.name.text.empty} and ${not #lists.isEmpty(result.resource.name.coding)} and ${not result.resource.name.coding[0].empty} and ${not result.resource.name.coding[0].display.empty}" th:text="${result.resource.name.coding[0].display}"/> <th:block th:if="${result.resource.name.text.empty} and ${not #lists.isEmpty(result.resource.name.coding)} and ${not result.resource.name.coding[0].empty} and ${not result.resource.name.coding[0].display.empty}" th:text="${result.resource.name.coding[0].display}"/>
<th:block th:if="${result.resource.name.text.empty} and ${not #lists.isEmpty(result.resource.name.coding)} and ${not result.resource.name.coding[0].empty} and ${result.resource.name.coding[0].display.empty}" th:text="?"/> <th:block th:if="${result.resource.name.text.empty} and ${not #lists.isEmpty(result.resource.name.coding)} and ${not result.resource.name.coding[0].empty} and ${result.resource.name.coding[0].display.empty}" th:text="'?'"/>
<!--/*--> Hb <!--*/--> <!--/*--> Hb <!--*/-->
</td> </td>
<td th:narrative="${result.resource.value}">2.2 g/L</td> <td th:narrative="${result.resource.value}">2.2 g/L</td>

View File

@ -12,6 +12,7 @@ import org.junit.Test;
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.api.TemporalPrecisionEnum; 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.NarrativeDt;
import ca.uhn.fhir.model.dstu.composite.PeriodDt; import ca.uhn.fhir.model.dstu.composite.PeriodDt;
import ca.uhn.fhir.model.dstu.composite.QuantityDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt;
@ -154,6 +155,11 @@ public class DefaultThymeleafNarrativeGeneratorTest {
obs.setValue(new StringDt("HELLO!")); obs.setValue(new StringDt("HELLO!"));
value.addResult().setResource(obs); 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); NarrativeDt generateNarrative = gen.generateNarrative("http://hl7.org/fhir/profiles/DiagnosticReport", value);
String output = generateNarrative.getDiv().getValueAsString(); String output = generateNarrative.getDiv().getValueAsString();
@ -161,8 +167,8 @@ public class DefaultThymeleafNarrativeGeneratorTest {
assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some &amp; Diagnostic Report </div>")); assertThat(output, StringContains.containsString("<div class=\"hapiHeaderText\"> Some &amp; Diagnostic Report </div>"));
String title = gen.generateTitle(value); String title = gen.generateTitle(value);
ourLog.info(title); // ourLog.info(title);
assertEquals("Some & Diagnostic Report - final - 2 observations", title); assertEquals("Some & Diagnostic Report - final - 3 observations", title);
// Now try it with the parser // Now try it with the parser

View File

@ -878,7 +878,12 @@ public class XmlParserTest {
"<link rel=\"self\" href=\"http://hl7.org/implement/standards/fhir/valuesets.xml\"/>" + "<link rel=\"self\" href=\"http://hl7.org/implement/standards/fhir/valuesets.xml\"/>" +
"<updated>2014-02-10T04:11:24.435+00:00</updated>" + "<updated>2014-02-10T04:11:24.435+00:00</updated>" +
"<at:deleted-entry xmlns:at=\"http://purl.org/atompub/tombstones/1.0\" ref=\"http://foo/Patient/1\" when=\"2013-02-10T04:11:24.435+00:00\">" + "<at:deleted-entry xmlns:at=\"http://purl.org/atompub/tombstones/1.0\" ref=\"http://foo/Patient/1\" when=\"2013-02-10T04:11:24.435+00:00\">" +
"<link rel=\"self\" href=\"http://foo/Patient/1/_history/2\"/>" + "<at:by>" +
"<at:name>John Doe</at:name>" +
"<at:email>jdoe@example.org</at:email>" +
"</at:by>" +
"<at:comment>Removed comment spam</at:comment>" +
"<link rel=\"self\" href=\"http://foo/Patient/1/_history/2\"/>" +
"</at:deleted-entry>" + "</at:deleted-entry>" +
"</feed>"; "</feed>";
//@formatter:on //@formatter:on
@ -893,6 +898,9 @@ public class XmlParserTest {
assertEquals("1", entry.getResource().getId().getIdPart()); assertEquals("1", entry.getResource().getId().getIdPart());
assertEquals("2", entry.getResource().getId().getVersionIdPart()); assertEquals("2", entry.getResource().getId().getVersionIdPart());
assertEquals("2", ((IdDt)entry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION_ID)).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)); 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)); ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(bundle));

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.model.dstu.resource.Encounter;
import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient; 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.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
@ -115,6 +116,23 @@ public class GenericClientTest {
return msg; return msg;
} }
private String getResourceResult() {
//@formatter:off
String msg =
"<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>"
+ "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>"
+ "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>"
+ "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>"
+ "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>"
+ "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>"
+ "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />"
+ "</Patient>";
//@formatter:on
return msg;
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Test @Test
public void testSearchByString() throws Exception { public void testSearchByString() throws Exception {
@ -406,6 +424,32 @@ public class GenericClientTest {
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()); 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<HttpUriRequest> 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") @SuppressWarnings("unused")
@Test @Test

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -22,6 +22,7 @@ import org.junit.Test;
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.resource.Patient; 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.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
@ -127,6 +128,19 @@ public class StringParameterTest {
} }
} }
@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 @AfterClass
public static void afterClass() throws Exception { public static void afterClass() throws Exception {
ourServer.stop(); ourServer.stop();
@ -190,6 +204,25 @@ public class StringParameterTest {
return retVal; return retVal;
} }
@Search
public List<Patient> findPatientWithOptional(@OptionalParam(name = "ccc") StringParam theParam) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
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 @Override
public Class<? extends IResource> getResourceType() { public Class<? extends IResource> getResourceType() {
return Patient.class; return Patient.class;

View File

@ -17,7 +17,7 @@
<value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value> <value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value>
<value>blaze , Blaze (Orion Health) , https://fhir.orionhealth.com/blaze/fhir</value> <value>blaze , Blaze (Orion Health) , https://fhir.orionhealth.com/blaze/fhir</value>
<value>oridashi , Oridashi , http://demo.oridashi.com.au:8190</value> <value>oridashi , Oridashi , http://demo.oridashi.com.au:8190</value>
<value>fhirbase , FHIRPlace (Health Samurai) , http://try-fhirplace.hospital-systems.com/ </value> <!-- <value>fhirbase , FHIRPlace (Health Samurai) , http://try-fhirplace.hospital-systems.com/ </value> -->
</list> </list>
</property> </property>
</bean> </bean>

View File

@ -82,9 +82,15 @@ public class Controller {
theModel.put("notHome", true); theModel.put("notHome", true);
theModel.put("extraBreadcrumb", "About"); theModel.put("extraBreadcrumb", "About");
ourLog.info(logPrefix(theModel) + "Displayed about page");
return "about"; return "about";
} }
private String logPrefix(ModelMap theModel) {
return "[server=" + theModel.get("serverId") + "] - ";
}
@RequestMapping(value = { "/conformance" }) @RequestMapping(value = { "/conformance" })
public String actionConformance(final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) { public String actionConformance(final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
addCommonParams(theRequest, theModel); addCommonParams(theRequest, theModel);
@ -102,6 +108,8 @@ public class Controller {
processAndAddLastClientInvocation(client, returnsResource, theModel, delay, "Loaded conformance"); processAndAddLastClientInvocation(client, returnsResource, theModel, delay, "Loaded conformance");
ourLog.info(logPrefix(theModel) + "Displayed conformance profile");
return "result"; return "result";
} }
@ -143,6 +151,8 @@ public class Controller {
long delay = System.currentTimeMillis() - start; long delay = System.currentTimeMillis() - start;
processAndAddLastClientInvocation(client, returnsResource, theModel, delay, outcomeDescription); processAndAddLastClientInvocation(client, returnsResource, theModel, delay, outcomeDescription);
ourLog.info(logPrefix(theModel) + "Deleted resource of type " + def.getName());
return "result"; return "result";
} }
@ -185,14 +195,18 @@ public class Controller {
String vid = theReq.getParameter("resource-tags-vid"); String vid = theReq.getParameter("resource-tags-vid");
if (isNotBlank(vid)) { if (isNotBlank(vid)) {
client.getTags().forResource(resType, id, vid).execute(); client.getTags().forResource(resType, id, vid).execute();
ourLog.info(logPrefix(theModel) + "Got tags for type " + def.getName() + " ID " + id + " version" + vid);
} else { } else {
client.getTags().forResource(resType, id).execute(); client.getTags().forResource(resType, id).execute();
ourLog.info(logPrefix(theModel) + "Got tags for type " + def.getName() + " ID " + id);
} }
} else { } else {
client.getTags().forResource(resType).execute(); client.getTags().forResource(resType).execute();
ourLog.info(logPrefix(theModel) + "Got tags for type " + def.getName());
} }
} else { } else {
client.getTags().execute(); client.getTags().execute();
ourLog.info(logPrefix(theModel) + "Got tags for server");
} }
} catch (Exception e) { } catch (Exception e) {
returnsResource = handleClientException(client, e, theModel); returnsResource = handleClientException(client, e, theModel);
@ -230,6 +244,7 @@ public class Controller {
String url = defaultString(theReq.getParameter("page-url")); String url = defaultString(theReq.getParameter("page-url"));
if (!url.startsWith(theModel.get("base").toString())) { if (!url.startsWith(theModel.get("base").toString())) {
ourLog.warn(logPrefix(theModel) + "Refusing to load page URL: {}", url);
theModel.put("errorMsg", "Invalid page URL: " + url); theModel.put("errorMsg", "Invalid page URL: " + url);
return "result"; return "result";
} }
@ -240,6 +255,7 @@ public class Controller {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
try { try {
ourLog.info(logPrefix(theModel) + "Loading paging URL: {}", url);
client.loadPage().url(url).execute(); client.loadPage().url(url).execute();
} catch (Exception e) { } catch (Exception e) {
returnsResource = handleClientException(client, e, theModel); returnsResource = handleClientException(client, e, theModel);
@ -284,7 +300,9 @@ public class Controller {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
try { 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) { } catch (Exception e) {
returnsResource = handleClientException(client, e, theModel); returnsResource = handleClientException(client, e, theModel);
} }
@ -360,6 +378,8 @@ public class Controller {
theModel.put("updateResourceId", updateId); theModel.put("updateResourceId", updateId);
} }
ourLog.info(logPrefix(theModel) + "Showing resource page: {}", resourceName);
return "resource"; return "resource";
} }
@ -371,8 +391,8 @@ public class Controller {
JsonGenerator clientCodeJsonWriter = Json.createGenerator(clientCodeJsonStringWriter); JsonGenerator clientCodeJsonWriter = Json.createGenerator(clientCodeJsonStringWriter);
clientCodeJsonWriter.writeStartObject(); clientCodeJsonWriter.writeStartObject();
clientCodeJsonWriter.write("action", "search"); clientCodeJsonWriter.write("action", "search");
clientCodeJsonWriter.write("base", (String)theModel.get("base")); clientCodeJsonWriter.write("base", (String) theModel.get("base"));
GenericClient client = theRequest.newClient(myCtx, myConfig); GenericClient client = theRequest.newClient(myCtx, myConfig);
IUntypedQuery search = client.search(); IUntypedQuery search = client.search();
@ -389,29 +409,45 @@ public class Controller {
query = search.forAllResources(); query = search.forAllResources();
clientCodeJsonWriter.writeNull("resource"); 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"; String outcomeDescription = "Search for Resources";
clientCodeJsonWriter.writeStartArray("params");
int paramIdx = -1; int paramIdx = -1;
while (true) { while (true) {
paramIdx++; paramIdx++;
String paramIdxString = Integer.toString(paramIdx); String paramIdxString = Integer.toString(paramIdx);
boolean shouldContinue = handleSearchParam(paramIdxString, theReq, query); boolean shouldContinue = handleSearchParam(paramIdxString, theReq, query, clientCodeJsonWriter);
if (!shouldContinue) { if (!shouldContinue) {
break; break;
} }
} }
clientCodeJsonWriter.writeEnd();
clientCodeJsonWriter.writeStartArray("includes");
String[] incValues = theReq.getParameterValues(Constants.PARAM_INCLUDE); String[] incValues = theReq.getParameterValues(Constants.PARAM_INCLUDE);
if (incValues != null) { if (incValues != null) {
for (String next : incValues) { for (String next : incValues) {
if (isNotBlank(next)) { if (isNotBlank(next)) {
query.include(new Include(next)); query.include(new Include(next));
clientCodeJsonWriter.write(next);
} }
} }
} }
clientCodeJsonWriter.writeEnd();
String limit = theReq.getParameter("resource-search-limit"); String limit = theReq.getParameter("resource-search-limit");
if (isNotBlank(limit)) { if (isNotBlank(limit)) {
@ -419,12 +455,18 @@ public class Controller {
theModel.put("errorMsg", "Search limit must be a numeric value."); theModel.put("errorMsg", "Search limit must be a numeric value.");
return "resource"; 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(); long start = System.currentTimeMillis();
ResultType returnsResource; ResultType returnsResource;
try { try {
ourLog.info(logPrefix(theModel) + "Executing a search");
query.execute(); query.execute();
returnsResource = ResultType.BUNDLE; returnsResource = ResultType.BUNDLE;
} catch (Exception e) { } catch (Exception e) {
@ -438,7 +480,7 @@ public class Controller {
clientCodeJsonWriter.close(); clientCodeJsonWriter.close();
String clientCodeJson = clientCodeJsonStringWriter.toString(); String clientCodeJson = clientCodeJsonStringWriter.toString();
theModel.put("clientCodeJson", clientCodeJson); theModel.put("clientCodeJson", clientCodeJson);
return "result"; return "result";
} }
@ -466,11 +508,17 @@ public class Controller {
return "home"; return "home";
} }
ResultType returnsResource = ResultType.BUNDLE;
long start = System.currentTimeMillis(); 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; long delay = System.currentTimeMillis() - start;
processAndAddLastClientInvocation(client, ResultType.BUNDLE, theModel, delay, "Transaction"); processAndAddLastClientInvocation(client, returnsResource, theModel, delay, "Transaction");
return "result"; return "result";
} }
@ -555,6 +603,7 @@ public class Controller {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
ResultType returnsResource = ResultType.RESOURCE; ResultType returnsResource = ResultType.RESOURCE;
outcomeDescription = ""; outcomeDescription = "";
boolean update = false;
try { try {
if (validate) { if (validate) {
outcomeDescription = "Validate Resource"; outcomeDescription = "Validate Resource";
@ -564,6 +613,7 @@ public class Controller {
if (isNotBlank(id)) { if (isNotBlank(id)) {
outcomeDescription = "Update Resource"; outcomeDescription = "Update Resource";
client.update(id, resource); client.update(id, resource);
update = true;
} else { } else {
outcomeDescription = "Create Resource"; outcomeDescription = "Create Resource";
client.create(resource); client.create(resource);
@ -576,6 +626,18 @@ public class Controller {
processAndAddLastClientInvocation(client, returnsResource, theModel, delay, outcomeDescription); 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) { private void doActionHistory(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel, String theMethod, String theMethodDescription) {
@ -603,11 +665,18 @@ public class Controller {
limit = Integer.parseInt(limitStr); limit = Integer.parseInt(limitStr);
} }
ResultType returnsResource = ResultType.BUNDLE;
long start = System.currentTimeMillis(); 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; long delay = System.currentTimeMillis() - start;
processAndAddLastClientInvocation(client, ResultType.BUNDLE, theModel, delay, theMethodDescription); processAndAddLastClientInvocation(client, returnsResource, theModel, delay, theMethodDescription);
} }
@ -770,42 +839,59 @@ public class Controller {
return def; 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"); String nextName = theReq.getParameter("param." + paramIdxString + ".name");
if (isBlank(nextName)) { if (isBlank(nextName)) {
return false; return false;
} }
String nextQualifier = StringUtils.defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier")); String nextQualifier = StringUtils.defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier"));
String nextType = theReq.getParameter("param." + paramIdxString + ".type"); String nextType = theReq.getParameter("param." + paramIdxString + ".type");
StringBuilder b = new StringBuilder(); List<String> parts = new ArrayList<String>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 5; i++) {
b.append(defaultString(theReq.getParameter("param." + paramIdxString + "." + i))); parts.add(defaultString(theReq.getParameter("param." + paramIdxString + "." + i)));
}
String paramValue = b.toString();
if (isBlank(paramValue)) {
return true;
} }
List<String> values;
if ("token".equals(nextType)) { 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<String>();
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; return true;
} }
} }
// if ("xml".equals(theReq.getParameter("encoding"))) { for (String nextValue : values) {
// query.encodedXml();
// }else if ("json".equals(theReq.getParameter("encoding"))) {
// query.encodedJson();
// }
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"))) { if (StringUtils.isNotBlank(theReq.getParameter("param." + paramIdxString + ".0.name"))) {
handleSearchParam(paramIdxString + ".0", theReq, theQuery); handleSearchParam(paramIdxString + ".0", theReq, theQuery, theClientCodeJsonWriter);
} }
return true; return true;

View File

@ -15,8 +15,9 @@
<value>home , Localhost Server , http://localhost:8887/fhir/context </value> <value>home , Localhost Server , http://localhost:8887/fhir/context </value>
<value>hi , Health Intersections , http://fhir.healthintersections.com.au/open</value> <value>hi , Health Intersections , http://fhir.healthintersections.com.au/open</value>
<value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value> <value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value>
<value>blaze , Blaze (Orion Health) , https://his-medicomp-gateway.orionhealth.com/blaze/fhir</value> <value>blaze , Blaze (Orion Health) , https://fhir.orionhealth.com/blaze/fhir</value>
<value>oridashi , Oridashi , http://demo.oridashi.com.au:8190</value> <value>oridashi , Oridashi , http://demo.oridashi.com.au:8190</value>
<value>fhirbase , FHIRPlace (Health Samurai) , http://try-fhirplace.hospital-systems.com/ </value>
</list> </list>
</property> </property>
</bean> </bean>

View File

@ -537,5 +537,11 @@
</form> </form>
<div th:replace="tmpl-footer :: footer" ></div> <div th:replace="tmpl-footer :: footer" ></div>
<!--
<script type="text/javascript">
$(function () { $("[data-toggle='tooltip']").tooltip(); });
</script>
-->
</body> </body>
</html> </html>

View File

@ -27,7 +27,6 @@
<th:block th:text="${latencyMs} + 'ms'"/> <th:block th:text="${latencyMs} + 'ms'"/>
</div> </div>
<!--
<div th:if="${clientCodeJson} != null" class="panel panel-default"> <div th:if="${clientCodeJson} != null" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
Client Code - Client Code -
@ -44,7 +43,6 @@
generateHapi(jsonClientCode, $('#clientCodeBody')); generateHapi(jsonClientCode, $('#clientCodeBody'));
</script> </script>
</div> </div>
-->
<div th:if="${requestUrl} != null"> <div th:if="${requestUrl} != null">

View File

@ -44,6 +44,7 @@
<div class="col-sm-6"> <div class="col-sm-6">
<div class="searchParamDescription"> <div class="searchParamDescription">
<div> <div>
<b th:if="${param.name} != null and ${!param.name.empty}" th:text="${param.name} + ' - '" />
<th:block th:text="${param.documentation}" /> <th:block th:text="${param.documentation}" />
<th:block th:if="${!param.getUndeclaredExtensionsByUrl(requiredParamExtension).empty}"> <th:block th:if="${!param.getUndeclaredExtensionsByUrl(requiredParamExtension).empty}">
<span style="color:#F88;" th:if="${param.getUndeclaredExtensionsByUrl(requiredParamExtension).get(0).value.value}"> <span style="color:#F88;" th:if="${param.getUndeclaredExtensionsByUrl(requiredParamExtension).get(0).value.value}">

View File

@ -22,11 +22,20 @@ body {
line-height: 0.85em; line-height: 0.85em;
} }
.clientCodeComment {
color: #4A4;
font-style: italic;
}
.clientCodePreamble { .clientCodePreamble {
color: #888; color: #888;
font-style: italic; font-style: italic;
} }
.clientCodeIndent {
margin-left: 15px;
}
label { label {
font-size: 1.0em; font-size: 1.0em;
color: #808080; color: #808080;

View File

@ -1,4 +1,4 @@
function generateHapi(json, container) { function generateHapi(json, container) {
if (json.action == 'search') { if (json.action == 'search') {
generateHapiSearch(json, container); generateHapiSearch(json, container);
@ -6,12 +6,17 @@ function generateHapi(json, container) {
} }
function generateHapiSearch(json, container) { function generateHapiSearch(json, container) {
container.append($('<span />', {'class': 'clientCodeComment'}).text("// Create a client (only needed once)"));
container.append($('<br/>'));
container.append($('<span />', {'class': 'clientCodePreamble'}).text("FhirContext ctx = new FhirContext();")); container.append($('<span />', {'class': 'clientCodePreamble'}).text("FhirContext ctx = new FhirContext();"));
container.append($('<br/>')); container.append($('<br/>'));
container.append($('<span />', {'class': 'clientCodePreamble'}).text("IGenericClient client = ctx.newRestfulGenericClient(\"" + json.base + "\");")); container.append($('<span />', {'class': 'clientCodePreamble'}).text("IGenericClient client = ctx.newRestfulGenericClient(\"" + json.base + "\");"));
container.append($('<br/>')); container.append($('<br/>'));
container.append($('<br/>'));
container.append($('<span />', {'class': 'clientCodeComment'}).text("// Invoke the client"));
container.append($('<br/>'));
var searchLine = 'client.search()'; var searchLine = 'Bundle bundle = client.search()';
if (json.resource != null) { if (json.resource != null) {
searchLine = searchLine + '.forResource(' + json.resource + '.class)'; searchLine = searchLine + '.forResource(' + json.resource + '.class)';
} else { } else {
@ -19,4 +24,60 @@ function generateHapiSearch(json, container) {
} }
container.append($('<span />', {'class': 'clientCodeMain'}).text(searchLine)); container.append($('<span />', {'class': 'clientCodeMain'}).text(searchLine));
var indented = $('<div />', {'class': 'clientCodeIndent'});
container.append(indented);
if (json.pretty) {
indented.append($('<span />', {'class': 'clientCodeMain'}).text('.setPrettyPrint(' + json.pretty + ')'));
indented.append($('<br/>'));
}
if (json.format) {
indented.append($('<span />', {'class': 'clientCodeMain'}).text('.setEncoding(EncodingEnum.' + json.format.toUpperCase() + ')'));
indented.append($('<br/>'));
}
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($('<span />', {'class': 'clientCodeMain'}).text(paramLine));
indented.append($('<br/>'));
}
}
for (var i = 0; i < json.includes.length; i++) {
indented.append($('<span />', {'class': 'clientCodeMain'}).text('.include(new Include("' + json.includes[i] + '"))'));
indented.append($('<br/>'));
}
if (json.limit) {
indented.append($('<span />', {'class': 'clientCodeMain'}).text('.limitTo(' + json.limit + ')'));
indented.append($('<br/>'));
}
indented.append($('<span />', {'class': 'clientCodeMain'}).text('.execute();'));
} }

View File

@ -95,55 +95,70 @@ function addSearchControls(theSearchParamType, theSearchParamName, theSearchPara
) )
); );
} else if (theSearchParamType == 'date') { } else if (theSearchParamType == 'date') {
var qualifier = $('<input />', {type:'hidden', id:'param.'+theRowNum+'.0', id:'param.'+theRowNum+'.0'}); addSearchControlDate(theSearchParamName, theContainerRowNum, theRowNum, true);
addSearchControlDate(theSearchParamName, theContainerRowNum, theRowNum, false);
if (/date$/.test(theSearchParamName)) {
var input = $('<div />', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DD' });
} else {
var input = $('<div />', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DDTHH:mm:ss' });
}
var qualifierDiv = $('<div />');
input.append(
qualifierDiv,
$('<input />', { type:'text', 'class':'form-control', id: 'param.' + theRowNum + '.1' }),
$('<div />', { 'class':'input-group-addon', 'style':'padding:6px;'} ).append(
$('<i />', { '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 = $('<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">=</button>');
var qualifierBtnEq = $('<a>=</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '='); });
var qualifierBtnGt = $('<a>&gt;</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '>'); });
var qualifierBtnGe = $('<a>&gt;=</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '>='); });
var qualifierBtnLt = $('<a>&lt;</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '<'); });
var qualifierBtnLe = $('<a>&lt;=</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '<='); });
qualifierDiv.append(
qualifierBtn,
$('<ul class="dropdown-menu" role="menu">').append(
$('<li />').append(qualifierBtnEq),
$('<li />').append(qualifierBtnGt),
$('<li />').append(qualifierBtnGe),
$('<li />').append(qualifierBtnLt),
$('<li />').append(qualifierBtnLe)
)
);
$('#search-param-rowopts-' + theContainerRowNum).append(
qualifier,
$('<div />', { 'class': 'col-sm-4' }).append(
input
)
);
} }
} }
function addSearchControlDate(theSearchParamName, theContainerRowNum, theRowNum, theLower) {
var inputId0 = theRowNum + '.' + (theLower ? 0 : 2);
var inputId1 = theRowNum + '.' + (theLower ? 1 : 3);
var qualifier = $('<input />', {type:'hidden', id:'param.'+inputId0, id:'param.'+inputId0});
if (/date$/.test(theSearchParamName)) {
var input = $('<div />', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DD' });
} else {
var input = $('<div />', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DDTHH:mm:ss' });
}
var qualifierDiv = $('<div />');
input.append(
qualifierDiv,
$('<input />', { type:'text', 'class':'form-control', id: 'param.' + inputId1 }),
$('<div />', { 'class':'input-group-addon', 'style':'padding:6px;'} ).append(
$('<i />', { '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 qualifierTooltip = "Set a qualifier and a date to specify a boundary date. Set two qualifiers and dates to specify a range.";
var qualifierBtn = $('<button />', {type:'button', 'class':'btn btn-default dropdown-toggle', 'data-toggle':'dropdown', 'data-placement':'top', 'title':qualifierTooltip}).text('=');
qualifierBtn.tooltip({
'selector': '',
'placement': 'top',
'container':'body'
});
var qualifierBtnEq = $('<a>=</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '='); });
var qualifierBtnGt = $('<a>&gt;</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '>'); });
var qualifierBtnGe = $('<a>&gt;=</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '>='); });
var qualifierBtnLt = $('<a>&lt;</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '<'); });
var qualifierBtnLe = $('<a>&lt;=</a>').click(function() { updateSearchDateQualifier(qualifierBtn, qualifier, '<='); });
qualifierDiv.append(
qualifierBtn,
$('<ul class="dropdown-menu" role="menu">').append(
$('<li />').append(qualifierBtnEq),
$('<li />').append(qualifierBtnGt),
$('<li />').append(qualifierBtnGe),
$('<li />').append(qualifierBtnLt),
$('<li />').append(qualifierBtnLe)
)
);
$('#search-param-rowopts-' + theContainerRowNum).append(
qualifier,
$('<div />', { 'class': 'col-sm-3' }).append(
input
)
);
}
function handleSearchParamTypeChange(select, params, rowNum) { function handleSearchParamTypeChange(select, params, rowNum) {
var oldVal = select.prevVal; var oldVal = select.prevVal;
var newVal = select.val(); var newVal = select.val();