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">
Add support for paging responses from RESTful servers.
</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>
</body>
</document>

View File

@ -80,6 +80,10 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
if (!val.startsWith("<")) {
val = "<div>" + val + "</div>";
}
if (val.startsWith("<?") && val.endsWith("?>")) {
myValue = null;
return;
}
try {
ArrayList<XMLEvent> value = new ArrayList<XMLEvent>();
@ -99,7 +103,7 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
setValue(value);
} 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) {
throw new ConfigurationException(e);
}

View File

@ -32,6 +32,7 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.message.BasicHeader;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.util.VersionUtil;
public abstract class BaseHttpClientInvocation {
@ -87,6 +88,11 @@ public abstract class BaseHttpClientInvocation {
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.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -46,7 +47,10 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
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 long serialVersionUID = 1L;
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 TemplateEngine myTemplateEngine;
@ -191,10 +196,15 @@ public class RestfulTesterServlet extends HttpServlet {
myTemplateEngine.initialize();
}
public void setServerBase(String theServerBase) {
myServerBase = theServerBase;
public void addServerBase(String theId, String theDisplayName, String 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 {
String resourceName = StringUtils.defaultString(theReq.getParameter(PARAM_RESOURCE));
RuntimeResourceDefinition def = myCtx.getResourceDefinition(resourceName);
@ -214,9 +224,9 @@ public class RestfulTesterServlet extends HttpServlet {
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);
ResultType returnsResource;
long latency = 0;
@ -285,7 +295,7 @@ public class RestfulTesterServlet extends HttpServlet {
} else if ("page".equals(method)) {
String url = defaultString(theReq.getParameter("page-url"));
if (!url.startsWith(myServerBase)) {
if (!url.startsWith(theServerBase)) {
theContext.getVariables().put("errorMsg", "Invalid page URL: " + url);
return;
}
@ -453,6 +463,13 @@ public class RestfulTesterServlet extends HttpServlet {
String resultStatus = client.getLastResponse() != null ? client.getLastResponse().getStatusLine().toString() : null;
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();
ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null;
String mimeType = ct != null ? ct.getMimeType() : null;
@ -591,7 +608,7 @@ public class RestfulTesterServlet extends HttpServlet {
if (nextChar == ':') {
inValue = true;
b.append(nextChar);
} else if (nextChar == '[' || nextChar == '[') {
} else if (nextChar == '[' || nextChar == '{') {
b.append("<span class='hlControl'>");
b.append(nextChar);
b.append("</span>");
@ -694,7 +711,18 @@ public class RestfulTesterServlet extends HttpServlet {
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();
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("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));
ctx.setVariable("resourceName", resourceName);
ctx.setVariable("jsonEncodedConf", myCtx.newJsonParser().encodeResourceToString(conformance));
addStandardVariables(ctx, theReq.getParameterMap());
if (isNotBlank(theReq.getParameter("action"))) {
processAction(theReq, ctx);
processAction(theReq, ctx, client, serverBase);
}
if (isNotBlank(resourceName)) {

View File

@ -40,6 +40,7 @@
<body>
<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="container-fluid">
@ -64,6 +65,18 @@
</div>
<div class="navbar-collapse collapse">
<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>
</ul>
</div>
@ -135,12 +148,17 @@
}
</script>
<h4>Query Mode</h4>
<h4>Server Home</h4>
<ul class="nav nav-sidebar">
<li th:class="${resourceName.empty} ? 'active' : ''">
<a href="#" onclick="doAction(this, 'home', null);">RESTful Server</a>
</li>
</ul>
<h4>Resources</h4>
<ul class="nav nav-sidebar">
<th:block
th:each="resource, resIterStat : ${conf.restFirstRep.resource}">
<li th:class="${resourceName} == ${resource.type.valueAsString} ? 'active' : ''">

View File

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

View File

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

View File

@ -25,10 +25,12 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.internal.matchers.Not;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
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
public void testNestedContainedResources() {

View File

@ -79,7 +79,6 @@ public class XmlParserTest {
}
@Test
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();
String base = "http://localhost:" + myPort + "/fhir/context";
// 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();
handler.setName("Tester");
handler.setServlet(testerServlet);