Fix a few parsing issues and add multi server support to tester

This commit is contained in:
jamesagnew 2014-06-27 09:05:51 -04:00
parent 4cd7ff16fb
commit 50d68d6090
11 changed files with 3498 additions and 29 deletions

View File

@ -56,6 +56,10 @@
<action type="add"> <action type="add">
Add support for paging responses from RESTful servers. Add support for paging responses from RESTful servers.
</action> </action>
<action type="fix">
Don't fail on narrative blocks in JSON resources with only an XML declaration but no content (these are
produced by the Health Intersections server)
</action>
</release> </release>
</body> </body>
</document> </document>

View File

@ -80,6 +80,10 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
if (!val.startsWith("<")) { if (!val.startsWith("<")) {
val = "<div>" + val + "</div>"; val = "<div>" + val + "</div>";
} }
if (val.startsWith("<?") && val.endsWith("?>")) {
myValue = null;
return;
}
try { try {
ArrayList<XMLEvent> value = new ArrayList<XMLEvent>(); ArrayList<XMLEvent> value = new ArrayList<XMLEvent>();
@ -99,7 +103,7 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
setValue(value); setValue(value);
} catch (XMLStreamException e) { } catch (XMLStreamException e) {
throw new DataFormatException("String does not appear to be valid XML/XHTML: "+e.getMessage(), e); throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \""+e.getMessage() + "\"): " + theValue, e);
} catch (FactoryConfigurationError e) { } catch (FactoryConfigurationError e) {
throw new ConfigurationException(e); throw new ConfigurationException(e);
} }

View File

@ -32,6 +32,7 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.util.VersionUtil;
public abstract class BaseHttpClientInvocation { public abstract class BaseHttpClientInvocation {
@ -87,6 +88,11 @@ public abstract class BaseHttpClientInvocation {
theHttpRequest.addHeader(next); theHttpRequest.addHeader(next);
} }
} }
theHttpRequest.addHeader("User-Agent", "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client)");
theHttpRequest.addHeader("Accept-Charset", "utf-8");
theHttpRequest.addHeader("Accept-Encoding", "gzip");
} }
} }

View File

@ -32,6 +32,7 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -46,7 +47,10 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
@ -93,7 +97,8 @@ public class RestfulTesterServlet extends HttpServlet {
private static final String PUBLIC_TESTER_RESULT_HTML = "/PublicTesterResult.html"; private static final String PUBLIC_TESTER_RESULT_HTML = "/PublicTesterResult.html";
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private FhirContext myCtx; private FhirContext myCtx;
private String myServerBase; private LinkedHashMap<String, String> myIdToServerName = new LinkedHashMap<String, String>();
private LinkedHashMap<String, String> myIdToServerBase = new LinkedHashMap<String, String>();
private HashMap<String, String> myStaticResources; private HashMap<String, String> myStaticResources;
private TemplateEngine myTemplateEngine; private TemplateEngine myTemplateEngine;
@ -191,10 +196,15 @@ public class RestfulTesterServlet extends HttpServlet {
myTemplateEngine.initialize(); myTemplateEngine.initialize();
} }
public void setServerBase(String theServerBase) { public void addServerBase(String theId, String theDisplayName, String theServerBase) {
myServerBase = theServerBase; Validate.notBlank(theId, "theId can not be blank");
Validate.notBlank(theDisplayName, "theDisplayName can not be blank");
Validate.notBlank(theServerBase, "theServerBase can not be blank");
myIdToServerBase.put(theId, theServerBase);
myIdToServerName.put(theId, theDisplayName);
} }
private RuntimeResourceDefinition getResourceType(HttpServletRequest theReq) throws ServletException { private RuntimeResourceDefinition getResourceType(HttpServletRequest theReq) throws ServletException {
String resourceName = StringUtils.defaultString(theReq.getParameter(PARAM_RESOURCE)); String resourceName = StringUtils.defaultString(theReq.getParameter(PARAM_RESOURCE));
RuntimeResourceDefinition def = myCtx.getResourceDefinition(resourceName); RuntimeResourceDefinition def = myCtx.getResourceDefinition(resourceName);
@ -214,9 +224,9 @@ public class RestfulTesterServlet extends HttpServlet {
RESOURCE, BUNDLE, TAGLIST, NONE RESOURCE, BUNDLE, TAGLIST, NONE
} }
private void processAction(HttpServletRequest theReq, WebContext theContext) { private void processAction(HttpServletRequest theReq, WebContext theContext, IGenericClient theClient, String theServerBase) {
GenericClient client = (GenericClient) myCtx.newRestfulGenericClient(myServerBase); GenericClient client = (GenericClient) theClient;
client.setKeepResponses(true); client.setKeepResponses(true);
ResultType returnsResource; ResultType returnsResource;
long latency = 0; long latency = 0;
@ -285,7 +295,7 @@ public class RestfulTesterServlet extends HttpServlet {
} else if ("page".equals(method)) { } else if ("page".equals(method)) {
String url = defaultString(theReq.getParameter("page-url")); String url = defaultString(theReq.getParameter("page-url"));
if (!url.startsWith(myServerBase)) { if (!url.startsWith(theServerBase)) {
theContext.getVariables().put("errorMsg", "Invalid page URL: " + url); theContext.getVariables().put("errorMsg", "Invalid page URL: " + url);
return; return;
} }
@ -453,6 +463,13 @@ public class RestfulTesterServlet extends HttpServlet {
String resultStatus = client.getLastResponse() != null ? client.getLastResponse().getStatusLine().toString() : null; String resultStatus = client.getLastResponse() != null ? client.getLastResponse().getStatusLine().toString() : null;
String resultBody = client.getLastResponseBody(); String resultBody = client.getLastResponseBody();
if (lastRequest instanceof HttpEntityEnclosingRequest) {
HttpEntity entity = ((HttpEntityEnclosingRequest) lastRequest).getEntity();
if (entity.isRepeatable()) {
requestBody = IOUtils.toString(entity.getContent());
}
}
HttpResponse lastResponse = client.getLastResponse(); HttpResponse lastResponse = client.getLastResponse();
ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null; ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null;
String mimeType = ct != null ? ct.getMimeType() : null; String mimeType = ct != null ? ct.getMimeType() : null;
@ -591,7 +608,7 @@ public class RestfulTesterServlet extends HttpServlet {
if (nextChar == ':') { if (nextChar == ':') {
inValue = true; inValue = true;
b.append(nextChar); b.append(nextChar);
} else if (nextChar == '[' || nextChar == '[') { } else if (nextChar == '[' || nextChar == '{') {
b.append("<span class='hlControl'>"); b.append("<span class='hlControl'>");
b.append(nextChar); b.append(nextChar);
b.append("</span>"); b.append("</span>");
@ -694,7 +711,18 @@ public class RestfulTesterServlet extends HttpServlet {
return; return;
} }
IGenericClient client = myCtx.newRestfulGenericClient(myServerBase); String serverId = theReq.getParameter("server-id");
String serverBase;
String serverName;
if (isBlank(serverId) && !myIdToServerBase.containsKey(serverId)) {
serverBase = myIdToServerBase.entrySet().iterator().next().getValue();
serverName = myIdToServerName.entrySet().iterator().next().getValue();
}else {
serverBase = myIdToServerBase.get(serverId);
serverName = myIdToServerName.get(serverId);
}
IGenericClient client = myCtx.newRestfulGenericClient(serverBase);
Conformance conformance = client.conformance(); Conformance conformance = client.conformance();
WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale()); WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale());
@ -736,16 +764,19 @@ public class RestfulTesterServlet extends HttpServlet {
} }
} }
ctx.setVariable("serverId", serverId);
ctx.setVariable("resourceCounts", resourceCounts); ctx.setVariable("resourceCounts", resourceCounts);
ctx.setVariable("conf", conformance); ctx.setVariable("conf", conformance);
ctx.setVariable("base", myServerBase); ctx.setVariable("base", serverBase);
ctx.setVariable("baseName", serverName);
ctx.setVariable("serverEntries", myIdToServerName.entrySet());
String resourceName = defaultString(theReq.getParameter(PARAM_RESOURCE)); String resourceName = defaultString(theReq.getParameter(PARAM_RESOURCE));
ctx.setVariable("resourceName", resourceName); ctx.setVariable("resourceName", resourceName);
ctx.setVariable("jsonEncodedConf", myCtx.newJsonParser().encodeResourceToString(conformance)); ctx.setVariable("jsonEncodedConf", myCtx.newJsonParser().encodeResourceToString(conformance));
addStandardVariables(ctx, theReq.getParameterMap()); addStandardVariables(ctx, theReq.getParameterMap());
if (isNotBlank(theReq.getParameter("action"))) { if (isNotBlank(theReq.getParameter("action"))) {
processAction(theReq, ctx); processAction(theReq, ctx, client, serverBase);
} }
if (isNotBlank(resourceName)) { if (isNotBlank(resourceName)) {

View File

@ -40,6 +40,7 @@
<body> <body>
<form action="" method="get" id="outerForm"> <form action="" method="get" id="outerForm">
<input type="hidden" id="server-id" name="server-id" th:value="${serverId}"/>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid"> <div class="container-fluid">
@ -64,6 +65,18 @@
</div> </div>
<div class="navbar-collapse collapse"> <div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-fire" style="color: #66AAFF"/>
<th:block th:text="'Server: ' + ${baseName}"/>
<span class="caret" />
</a>
<ul class="dropdown-menu" role="menu">
<li th:each="serverEntry : ${serverEntries}">
<a th:href="'javascript:selectServer(\'' + ${serverEntry.key} + '\');'" th:text="${serverEntry.value}">Action</a>
</li>
</ul>
</li>
<li><a href="#">Help</a></li> <li><a href="#">Help</a></li>
</ul> </ul>
</div> </div>
@ -135,12 +148,17 @@
} }
</script> </script>
<h4>Query Mode</h4> <h4>Server Home</h4>
<ul class="nav nav-sidebar"> <ul class="nav nav-sidebar">
<li th:class="${resourceName.empty} ? 'active' : ''"> <li th:class="${resourceName.empty} ? 'active' : ''">
<a href="#" onclick="doAction(this, 'home', null);">RESTful Server</a> <a href="#" onclick="doAction(this, 'home', null);">RESTful Server</a>
</li> </li>
</ul>
<h4>Resources</h4>
<ul class="nav nav-sidebar">
<th:block <th:block
th:each="resource, resIterStat : ${conf.restFirstRep.resource}"> th:each="resource, resIterStat : ${conf.restFirstRep.resource}">
<li th:class="${resourceName} == ${resource.type.valueAsString} ? 'active' : ''"> <li th:class="${resourceName} == ${resource.type.valueAsString} ? 'active' : ''">

View File

@ -230,10 +230,10 @@ PRE.resultBodyPlaceholder {
padding: 0px; padding: 0px;
} }
SPAN.searchParamDescription { DIV.searchParamDescription {
font-size: 0.8em; font-size: 0.8em;
text-align: justify; text-align: justify;
color: #000066; color: #668;
} }
DIV.searchParamSeparator { DIV.searchParamSeparator {

View File

@ -45,7 +45,7 @@ function addSearchParamRow() {
if (restResource.searchParam) { if (restResource.searchParam) {
for (var i = 0; i < restResource.searchParam.length; i++) { for (var i = 0; i < restResource.searchParam.length; i++) {
var searchParam = restResource.searchParam[i]; var searchParam = restResource.searchParam[i];
params[searchParam.name] = searchParam; params['_' + searchParam.name] = searchParam;
select.append( select.append(
$('<option />', { value: searchParam.name }).text(searchParam.name + ' - ' + searchParam.documentation) $('<option />', { value: searchParam.name }).text(searchParam.name + ' - ' + searchParam.documentation)
); );
@ -79,15 +79,15 @@ function addSearchParamRow() {
select.change(function(){ handleSearchParamTypeChange(select, params, nextRow); }); select.change(function(){ handleSearchParamTypeChange(select, params, nextRow); });
} }
function addSerarchControls(searchParam, searchParamName, containerRowNum, rowNum) { function addSearchControls(searchParam, searchParamName, containerRowNum, rowNum, isChild) {
$('#search-param-rowopts-' + containerRowNum).append( if (searchParam.childParam || isChild) {
$('<br clear="all"/>'), $('#search-param-rowopts-' + containerRowNum).append(
$('<div class="searchParamSeparator"/>'), $('<br clear="all"/>'),
$('<div />', { 'class': 'col-sm-6' }).append( $('<div class="searchParamSeparator"/>'),
$('<span class="searchParamDescription">' + searchParam.documentation + '</span>') $('<div />', { 'class': 'col-sm-6 searchParamDescription' }).text(searchParam.documentation)
) );
); }
if (searchParam.chain && searchParam.chain.length > 0) { if (searchParam.chain && searchParam.chain.length > 0) {
$('#search-param-rowopts-' + containerRowNum).append( $('#search-param-rowopts-' + containerRowNum).append(
$('<input />', { name: 'param.' + rowNum + '.qualifier', type: 'hidden', value: '.' + searchParam.chain[0] }) $('<input />', { name: 'param.' + rowNum + '.qualifier', type: 'hidden', value: '.' + searchParam.chain[0] })
@ -167,7 +167,7 @@ function addSerarchControls(searchParam, searchParamName, containerRowNum, rowNu
*/ */
$('<input />', { name: 'param.' + rowNum + '.0.type', type: 'hidden', value: searchParam.childParam.type }) $('<input />', { name: 'param.' + rowNum + '.0.type', type: 'hidden', value: searchParam.childParam.type })
); );
addSerarchControls(searchParam.childParam, searchParamName, containerRowNum, rowNum + '.0'); addSearchControls(searchParam.childParam, searchParamName, containerRowNum, rowNum + '.0', true);
} }
@ -180,16 +180,25 @@ function handleSearchParamTypeChange(select, params, rowNum) {
return; return;
} }
$('#search-param-rowopts-' + rowNum).empty(); $('#search-param-rowopts-' + rowNum).empty();
var searchParam = params[newVal]; var searchParam = params['_' + newVal];
$('#search-param-rowopts-' + rowNum).append( $('#search-param-rowopts-' + rowNum).append(
$('<input />', { name: 'param.' + rowNum + '.type', type: 'hidden', value: searchParam.type }) $('<input />', { name: 'param.' + rowNum + '.type', type: 'hidden', value: searchParam.type })
); );
addSerarchControls(searchParam, searchParam.name, rowNum, rowNum); addSearchControls(searchParam, searchParam.name, rowNum, rowNum, false);
select.prevVal = newVal; select.prevVal = newVal;
} }
function selectServer(serverId) {
$('#server-id').val(serverId);
$('#outerForm').append(
$('<input type="hidden" name="action" value="home"/>')
);
$('#outerForm').submit();
}
$( document ).ready(function() { $( document ).ready(function() {
addSearchParamRow(); addSearchParamRow();
}); });

View File

@ -25,10 +25,12 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.internal.matchers.Not; import org.mockito.internal.matchers.Not;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Child;
@ -84,6 +86,25 @@ public class JsonParserTest {
} }
@Test
public void testParseEmptyNarrative() throws ConfigurationException, DataFormatException, IOException {
String text = "{\n" +
" \"resourceType\" : \"Patient\",\n" +
" \"extension\" : [\n" +
" {\n" +
" \"url\" : \"http://clairol.org/colour\",\n" +
" \"valueCode\" : \"B\"\n" +
" }\n" +
" ],\n" +
" \"text\" : {\n" +
" \"div\" : \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"\n" +
" }" +
"}";
IResource res = ourCtx.newJsonParser().parseResource(text);
}
@Test @Test
public void testNestedContainedResources() { public void testNestedContainedResources() {

View File

@ -79,7 +79,6 @@ public class XmlParserTest {
} }
@Test @Test
public void testNestedContainedResources() { public void testNestedContainedResources() {

File diff suppressed because it is too large Load Diff

View File

@ -88,7 +88,13 @@ public class JpaTestApp {
RestfulTesterServlet testerServlet = new RestfulTesterServlet(); RestfulTesterServlet testerServlet = new RestfulTesterServlet();
String base = "http://localhost:" + myPort + "/fhir/context"; String base = "http://localhost:" + myPort + "/fhir/context";
// base = "http://fhir.healthintersections.com.au/open"; // base = "http://fhir.healthintersections.com.au/open";
testerServlet.setServerBase(base); // base = "http://spark.furore.com/fhir";
testerServlet.addServerBase("local", "Localhost Server", base);
testerServlet.addServerBase("hi", "Health Intersections", "http://fhir.healthintersections.com.au/open");
testerServlet.addServerBase("furore", "Spark - Furore Reference Server", "http://spark.furore.com/fhir");
testerServlet.addServerBase("blaze", "Blaze (Orion Health)", "https://his-medicomp-gateway.orionhealth.com/blaze/fhir");
ServletHolder handler = new ServletHolder(); ServletHolder handler = new ServletHolder();
handler.setName("Tester"); handler.setName("Tester");
handler.setServlet(testerServlet); handler.setServlet(testerServlet);