Fix #280 - Don't leave web testing UI buttons disabled when you return to a page via the back button

This commit is contained in:
jamesagnew 2016-03-01 07:15:02 -05:00
parent 496d866f48
commit c44b23f493
15 changed files with 418 additions and 177 deletions

View File

@ -70,14 +70,6 @@
<scope>test</scope>
</dependency>
<!-- Database -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<!-- Test Database -->
<dependency>
<groupId>org.apache.derby</groupId>
@ -89,7 +81,6 @@
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
@ -156,6 +147,11 @@
<artifactId>jetty-util</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -28,11 +28,11 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.HttpEntityWrapper;
import org.hl7.fhir.dstu3.model.DecimalType;
import org.hl7.fhir.dstu3.model.Extension;
import org.apache.http.message.BasicHeader;
import org.hl7.fhir.dstu3.model.Conformance.ConformanceRestComponent;
import org.hl7.fhir.dstu3.model.Conformance.ConformanceRestResourceComponent;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.dstu3.model.DecimalType;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.springframework.beans.factory.annotation.Autowired;
@ -43,15 +43,15 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
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.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.Conformance.Rest;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.client.GenericClient;
import ca.uhn.fhir.rest.client.IClientInterceptor;
import ca.uhn.fhir.rest.client.apache.ApacheHttpRequest;
import ca.uhn.fhir.rest.client.apache.ApacheHttpResponse;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.server.EncodingEnum;
@ -79,7 +79,7 @@ public class BaseController {
if (myConfig.getDebugTemplatesMode()) {
myTemplateEngine.getCacheManager().clearAllCaches();
}
final String serverId = theRequest.getServerIdWithDefault(myConfig);
final String serverBase = theRequest.getServerBase(theServletRequest, myConfig);
final String serverName = theRequest.getServerName(myConfig);
@ -91,7 +91,7 @@ public class BaseController {
theModel.put("pretty", theRequest.getPretty());
theModel.put("_summary", theRequest.get_summary());
theModel.put("serverEntries", myConfig.getIdToServerName());
return loadAndAddConf(theServletRequest, theRequest, theModel);
}
@ -108,16 +108,28 @@ public class BaseController {
return retVal.toArray(new Header[retVal.size()]);
}
private Header[] applyHeaderFilters(Map<String, List<String>> theAllHeaders) {
ArrayList<Header> retVal = new ArrayList<Header>();
for (String nextKey : theAllHeaders.keySet()) {
for (String nextValue : theAllHeaders.get(nextKey)) {
if (myFilterHeaders == null || !myFilterHeaders.contains(nextKey.toLowerCase())) {
retVal.add(new BasicHeader(nextKey, nextValue));
}
}
}
return retVal.toArray(new Header[retVal.size()]);
}
private String format(String theResultBody, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
return str;
}
StringBuilder b = new StringBuilder();
if (theEncodingEnum == EncodingEnum.JSON) {
boolean inValue = false;
boolean inQuote = false;
for (int i = 0; i < str.length(); i++) {
@ -171,7 +183,7 @@ public class BaseController {
}
}
}
} else {
boolean inQuote = false;
boolean inTag = false;
@ -215,7 +227,7 @@ public class BaseController {
}
}
}
return b.toString();
}
@ -224,16 +236,16 @@ public class BaseController {
if (str == null) {
return str;
}
try {
str = URLDecoder.decode(str, "UTF-8");
} catch (UnsupportedEncodingException e) {
ourLog.error("Should not happen", e);
}
StringBuilder b = new StringBuilder();
b.append("<span class='hlUrlBase'>");
boolean inParams = false;
for (int i = 0; i < str.length(); i++) {
char nextChar = str.charAt(i);
@ -261,7 +273,7 @@ public class BaseController {
}
}
}
if (inParams) {
b.append("</span>");
}
@ -291,11 +303,11 @@ public class BaseController {
ResultType returnsResource;
returnsResource = ResultType.NONE;
ourLog.warn("Failed to invoke server", e);
if (e != null) {
theModel.put("errorMsg", "Error: " + e.getMessage());
}
return returnsResource;
}
@ -314,7 +326,7 @@ public class BaseController {
private Conformance loadAndAddConfDstu1(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) {
CaptureInterceptor interceptor = new CaptureInterceptor();
GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, interceptor);
Conformance conformance;
try {
conformance = (Conformance) client.conformance();
@ -323,9 +335,9 @@ public class BaseController {
theModel.put("errorMsg", "Failed to load conformance statement, error was: " + e.toString());
conformance = new Conformance();
}
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance));
Map<String, Number> resourceCounts = new HashMap<String, Number>();
long total = 0;
for (Rest nextRest : conformance.getRest()) {
@ -339,7 +351,7 @@ public class BaseController {
}
}
theModel.put("resourceCounts", resourceCounts);
if (total > 0) {
for (Rest nextRest : conformance.getRest()) {
Collections.sort(nextRest.getResource(), new Comparator<ca.uhn.fhir.model.dstu.resource.Conformance.RestResource>() {
@ -364,17 +376,17 @@ public class BaseController {
});
}
}
theModel.put("conf", conformance);
theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED);
return conformance;
}
private IResource loadAndAddConfDstu2(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) {
CaptureInterceptor interceptor = new CaptureInterceptor();
GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, interceptor);
ca.uhn.fhir.model.dstu2.resource.Conformance conformance;
try {
conformance = (ca.uhn.fhir.model.dstu2.resource.Conformance) client.conformance();
@ -383,9 +395,9 @@ public class BaseController {
theModel.put("errorMsg", "Failed to load conformance statement, error was: " + e.toString());
conformance = new ca.uhn.fhir.model.dstu2.resource.Conformance();
}
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance));
Map<String, Number> resourceCounts = new HashMap<String, Number>();
long total = 0;
for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) {
@ -399,7 +411,7 @@ public class BaseController {
}
}
theModel.put("resourceCounts", resourceCounts);
if (total > 0) {
for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) {
Collections.sort(nextRest.getResource(), new Comparator<ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource>() {
@ -424,17 +436,17 @@ public class BaseController {
});
}
}
theModel.put("conf", conformance);
theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED);
return conformance;
}
private IBaseResource loadAndAddConfDstu3(HttpServletRequest theServletRequest, final HomeRequest theRequest, final ModelMap theModel) {
CaptureInterceptor interceptor = new CaptureInterceptor();
GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, interceptor);
org.hl7.fhir.dstu3.model.Conformance conformance;
try {
conformance = client.fetchConformance().ofType(org.hl7.fhir.dstu3.model.Conformance.class).execute();
@ -443,9 +455,9 @@ public class BaseController {
theModel.put("errorMsg", "Failed to load conformance statement, error was: " + e.toString());
conformance = new org.hl7.fhir.dstu3.model.Conformance();
}
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance));
Map<String, Number> resourceCounts = new HashMap<String, Number>();
long total = 0;
for (ConformanceRestComponent nextRest : conformance.getRest()) {
@ -459,7 +471,7 @@ public class BaseController {
}
}
theModel.put("resourceCounts", resourceCounts);
if (total > 0) {
for (ConformanceRestComponent nextRest : conformance.getRest()) {
Collections.sort(nextRest.getResource(), new Comparator<ConformanceRestResourceComponent>() {
@ -484,12 +496,13 @@ public class BaseController {
});
}
}
theModel.put("conf", conformance);
theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED);
return conformance;
}
protected String logPrefix(ModelMap theModel) {
return "[server=" + theModel.get("serverId") + "] - ";
}
@ -502,7 +515,7 @@ public class BaseController {
IResource resource = (IResource) par;
retVal = resource.getText().getDiv().getValueAsString();
} else if (par instanceof IDomainResource) {
retVal = ((IDomainResource)par).getText().getDivAsString();
retVal = ((IDomainResource) par).getText().getDivAsString();
} else {
retVal = null;
}
@ -518,7 +531,7 @@ public class BaseController {
return "";
}
String retVal = theBody.trim();
StringBuilder b = new StringBuilder();
for (int i = 0; i < retVal.length(); i++) {
char nextChar = retVal.charAt(i);
@ -543,30 +556,30 @@ public class BaseController {
protected void processAndAddLastClientInvocation(GenericClient theClient, ResultType theResultType, ModelMap theModelMap, long theLatency, String outcomeDescription, CaptureInterceptor theInterceptor, HomeRequest theRequest) {
try {
HttpRequestBase lastRequest = theInterceptor.getLastRequest();
ApacheHttpRequest lastRequest = theInterceptor.getLastRequest();
HttpResponse lastResponse = theInterceptor.getLastResponse();
String requestBody = null;
String requestUrl = lastRequest != null ? lastRequest.getURI().toASCIIString() : null;
String action = lastRequest != null ? lastRequest.getMethod() : null;
String requestUrl = lastRequest != null ? lastRequest.getApacheRequest().getURI().toASCIIString() : null;
String action = lastRequest != null ? lastRequest.getApacheRequest().getMethod() : null;
String resultStatus = lastResponse != null ? lastResponse.getStatusLine().toString() : null;
String resultBody = StringUtils.defaultString(theInterceptor.getLastResponseBody());
if (lastRequest instanceof HttpEntityEnclosingRequest) {
HttpEntity entity = ((HttpEntityEnclosingRequest) lastRequest).getEntity();
if (entity.isRepeatable()) {
requestBody = IOUtils.toString(entity.getContent());
}
}
ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null;
String mimeType = ct != null ? ct.getMimeType() : null;
EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType);
String narrativeString = "";
StringBuilder resultDescription = new StringBuilder();
Bundle bundle = null;
IBaseResource riBundle = null;
FhirContext context = getContext(theRequest);
if (ctEnum == null) {
resultDescription.append("Non-FHIR response");
@ -601,70 +614,70 @@ public class BaseController {
break;
}
}
resultDescription.append(" (").append(resultBody.length() + " bytes)");
Header[] requestHeaders = lastRequest != null ? applyHeaderFilters(lastRequest.getAllHeaders()) : new Header[0];
Header[] responseHeaders = lastResponse != null ? applyHeaderFilters(lastResponse.getAllHeaders()) : new Header[0];
theModelMap.put("outcomeDescription", outcomeDescription);
theModelMap.put("resultDescription", resultDescription.toString());
theModelMap.put("action", action);
theModelMap.put("bundle", bundle);
theModelMap.put("riBundle", riBundle);
theModelMap.put("resultStatus", resultStatus);
theModelMap.put("requestUrl", requestUrl);
theModelMap.put("requestUrlText", formatUrl(theClient.getUrlBase(), requestUrl));
String requestBodyText = format(requestBody, ctEnum);
theModelMap.put("requestBody", requestBodyText);
String resultBodyText = format(resultBody, ctEnum);
theModelMap.put("resultBody", resultBodyText);
theModelMap.put("resultBodyIsLong", resultBodyText.length() > 1000);
theModelMap.put("requestHeaders", requestHeaders);
theModelMap.put("responseHeaders", responseHeaders);
theModelMap.put("narrative", narrativeString);
theModelMap.put("latencyMs", theLatency);
} catch (Exception e) {
ourLog.error("Failure during processing", e);
theModelMap.put("errorMsg", "Error during processing: " + e.getMessage());
}
}
public static class CaptureInterceptor implements IClientInterceptor {
private HttpRequestBase myLastRequest;
private ApacheHttpRequest myLastRequest;
private HttpResponse myLastResponse;
private String myResponseBody;
public HttpRequestBase getLastRequest() {
public ApacheHttpRequest getLastRequest() {
return myLastRequest;
}
public HttpResponse getLastResponse() {
return myLastResponse;
}
public String getLastResponseBody() {
return myResponseBody;
}
@Override
public void interceptRequest(IHttpRequest theRequest) {
assert myLastRequest == null;
myLastRequest = (HttpRequestBase) theRequest;
myLastRequest = (ApacheHttpRequest) theRequest;
}
@Override
public void interceptResponse(IHttpResponse theResponse) throws IOException {
assert myLastResponse == null;
myLastResponse = (HttpResponse) theResponse;
myLastResponse = ((ApacheHttpResponse) theResponse).getResponse();
HttpEntity respEntity = myLastResponse.getEntity();
if (respEntity != null) {
final byte[] bytes;
@ -673,37 +686,37 @@ public class BaseController {
} catch (IllegalStateException e) {
throw new InternalErrorException(e);
}
myResponseBody = new String(bytes, "UTF-8");
myLastResponse.setEntity(new MyEntityWrapper(respEntity, bytes));
}
}
private static class MyEntityWrapper extends HttpEntityWrapper {
private byte[] myBytes;
public MyEntityWrapper(HttpEntity theWrappedEntity, byte[] theBytes) {
super(theWrappedEntity);
myBytes = theBytes;
}
@Override
public InputStream getContent() throws IOException {
return new ByteArrayInputStream(myBytes);
}
@Override
public void writeTo(OutputStream theOutstream) throws IOException {
theOutstream.write(myBytes);
}
}
}
protected enum ResultType {
BUNDLE, NONE, RESOURCE, TAGLIST
}
BUNDLE, NONE, RESOURCE, TAGLIST
}
}

View File

@ -1,7 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head th:include="tmpl-head :: head">
<title>RESTful Tester</title>
<head>
<title th:include="window-title :: home" />
<th:block th:include="tmpl-head :: head" />
<script th:include="tmpl-buttonclick-handler :: handler" />
</head>
<body>
@ -68,18 +70,17 @@
</div>
<div class="row-fluid">
<div class="col-sm-3 form-group">
<button type="button" id="fetch-conformance-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<a type="button" id="fetch-conformance-btn"
class="btn btn-primary btn-block">
<i class="fa fa-dot-circle-o"></i>
Conformance
</button>
</a>
<script type="text/javascript">
$('#fetch-conformance-btn').click(
function() {
var btn = $(this);
btn.button('loading');
$("#outerForm").attr("action", "conformance").submit();
});
function() {
handleActionButtonClick($(this));
$("#outerForm").attr("action", "conformance").submit();
});
</script>
</div>
</div>
@ -94,7 +95,7 @@
<div class="row-fluid top-buffer">
<div class="col-sm-3">
<button type="button" id="server-history-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
class="btn btn-primary btn-block">
<i class="fa fa-calendar"></i>
History
</button>
@ -134,7 +135,7 @@
$('#server-history-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var limit = $('#server-history-limit').val();
if (limit != null) btn.append($('<input />', { type: 'hidden', name: 'limit', value: limit }));
var since = $('#server-history-since').val();
@ -154,8 +155,7 @@
</div>
<div class="row-fluid">
<div class="col-sm-3">
<button type="button" id="transaction-btn"
data-loading-text="Processing &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<button type="button" id="transaction-btn" class="btn btn-primary btn-block">
<i class="fa fa-files-o"></i>
Transaction
</button>
@ -182,7 +182,7 @@
$('#transaction-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var id = $('#transaction-id').val();
if (id != null) btn.append($('<input />', { type: 'hidden', name: 'resource-create-id', value: id }));
var body = $('#transaction-body').val();
@ -208,8 +208,7 @@
</div>
<div class="row-fluid">
<div class="col-sm-3 form-group">
<button type="button" id="get-server-tags-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<button type="button" id="get-server-tags-btn" class="btn btn-primary btn-block">
<i class="fa fa-tags"></i>
Get Tags
</button>
@ -217,7 +216,7 @@
$('#get-server-tags-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
$("#outerForm").attr("action", "get-tags").submit();
});
</script>

View File

@ -1,7 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head th:include="tmpl-head :: head">
<title>RESTful Tester</title>
<head>
<title th:include="window-title :: resource" />
<th:block th:include="tmpl-head :: head" />
<script th:include="tmpl-buttonclick-handler :: handler" />
</head>
<body>
@ -91,8 +93,7 @@
<div class="container-fluid">
<div class="row-fluid">
<div class="col-sm-2" style="padding-left: 0px;">
<button type="button" id="search-btn" class="btn btn-primary btn-block"
data-loading-text="Searching &lt;i class='fa fa-spinner fa-spin'/&gt;">
<button type="button" id="search-btn" class="btn btn-primary btn-block">
<span class="glyphicon glyphicon-search"></span>
Search
</button>
@ -101,7 +102,7 @@
<script type="text/javascript">
$('#search-btn').click(function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
$('#tab-search').find('input').each(function() {
if (this.id) {
if (this.id.substring(0,4) == 'inc_') {
@ -248,7 +249,7 @@
<div class="row-fluid">
<div class="col-sm-2">
<button type="button" id="read-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
class="btn btn-primary btn-block">
<i class="fa fa-book"></i>
Read
</button>
@ -278,7 +279,7 @@
$('#read-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var id = $('#read-id').val();
if (id != null) btn.append($('<input />', { type: 'hidden', name: 'id', value: id }));
var vid = $('#read-vid').val();
@ -298,8 +299,7 @@
</div>
<div class="row-fluid top-buffer">
<div class="col-sm-2">
<button type="button" id="resource-history-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block"
<button type="button" id="resource-history-btn" class="btn btn-primary btn-block"
name="action-history-type">
<i class="fa fa-calendar"></i>
History
@ -350,7 +350,7 @@
$('#resource-history-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var limit = $('#resource-history-limit').val();
if (limit != null) btn.append($('<input />', { type: 'hidden', name: 'limit', value: limit }));
var since = $('#resource-history-since').val();
@ -370,8 +370,7 @@
</div>
<div class="row-fluid top-buffer">
<div class="col-sm-2">
<button type="button" id="resource-delete-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<button type="button" id="resource-delete-btn" class="btn btn-primary btn-block">
<i class="fa fa-trash-o"></i>
Delete
</button>
@ -391,7 +390,7 @@
$('#resource-delete-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var id = $('#resource-delete-id').val();
if (id != null) btn.append($('<input />', { type: 'hidden', name: 'resource-delete-id', value: id }));
$("#outerForm").attr("action", "delete").submit();
@ -409,7 +408,7 @@
<div class="row-fluid top-buffer">
<div class="col-sm-2">
<button type="button" id="resource-create-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
class="btn btn-primary btn-block">
<i class="fa fa-send"></i>
Create
</button>
@ -445,7 +444,7 @@
$('#resource-create-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var id = $('#resource-create-id').val();
if (id != null) btn.append($('<input />', { type: 'hidden', name: 'resource-create-id', value: id }));
var body = $('#resource-create-body').val();
@ -470,8 +469,7 @@
</div>
<div class="row-fluid top-buffer">
<div class="col-sm-2">
<button type="button" id="resource-update-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<button type="button" id="resource-update-btn" class="btn btn-primary btn-block">
<i class="fa fa-send"></i>
Update
</button>
@ -509,7 +507,7 @@
$('#resource-update-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var id = $('#resource-update-id').val();
// Note we're using resource-create-id even though this is an update because
// the controller expects that...
@ -538,8 +536,7 @@
</div>
<div class="row-fluid top-buffer">
<div class="col-sm-2">
<button type="button" id="resource-validate-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<button type="button" id="resource-validate-btn" class="btn btn-primary btn-block">
<i class="fa fa-thumbs-up"></i>
Validate
</button>
@ -564,7 +561,7 @@
$('#resource-validate-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var body = $('#resource-validate-body').val();
btn.append($('<input />', { type: 'hidden', name: 'resource-validate-body', value: body }));
$("#outerForm").attr("action", "validate").attr("method", "POST").submit();
@ -591,8 +588,7 @@
</div>
<div class="row-fluid">
<div class="col-sm-2">
<button type="button" id="get-resource-tags-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<button type="button" id="get-resource-tags-btn" class="btn btn-primary btn-block">
<i class="fa fa-tags"></i>
Get Tags
</button>
@ -621,7 +617,7 @@
$('#get-resource-tags-btn').click(
function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var id = $('#resource-tags-id').val();
if (id != null) btn.append($('<input />', { type: 'hidden', name: 'resource-tags-id', value: id }));
var vid = $('#resource-tags-vid').val();

View File

@ -1,7 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head th:include="tmpl-head :: head">
<title>RESTful Tester</title>
<head>
<title th:include="window-title :: result" />
<th:block th:include="tmpl-head :: head" />
<script th:include="tmpl-buttonclick-handler :: handler" />
</head>
<body>
@ -136,7 +138,7 @@
<!-- Prev/Next Page Buttons -->
<button class="btn btn-success btn-xs" type="button" id="page-prev-btn"
style="margin-left: 15px;" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;">
style="margin-left: 15px;">
<span class="glyphicon glyphicon-step-backward"></span>
Prev Page
</button>
@ -146,15 +148,14 @@
}
$('#page-prev-btn').click(function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
btn.append($('<input />', { type: 'hidden', name: 'page-url', value: '<th:block th:text="${bundle.linkPrevious}"/>' }));
$("#outerForm").attr("action", "page").submit();
});
</script>
<button class="btn btn-success btn-xs" type="button" id="page-next-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;">
<button class="btn btn-success btn-xs" type="button" id="page-next-btn">
<span class="glyphicon glyphicon-step-forward"></span>
Next Page
</button>
@ -164,7 +165,7 @@
}
$('#page-next-btn').click(function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
btn.append($('<input />', { type: 'hidden', name: 'page-url', value: '<th:block th:text="${bundle.linkNext}"/>' }));
$("#outerForm").attr("action", "page").submit();
});
@ -193,8 +194,8 @@
<tr th:each="entry : ${bundle.entries}">
<td style="white-space: nowrap;">
<th:block th:if="${entry.resource} != null">
<button class="btn btn-primary btn-xs" th:onclick="'readFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart,'')} + '\');'" type="submit" name="action" value="read" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ><i class="fa fa-book"></i> Read</button>
<button class="btn btn-primary btn-xs" th:onclick="'updateFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart, '')} + '\');'" type="submit" name="action" value="home"><i class="fa fa-pencil" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ></i> Update</button>
<button class="btn btn-primary btn-xs" th:onclick="'readFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart,'')} + '\');'" type="submit" name="action" value="read"><i class="fa fa-book"></i> Read</button>
<button class="btn btn-primary btn-xs" th:onclick="'updateFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart, '')} + '\');'" type="submit" name="action" value="home"><i class="fa fa-pencil"></i> Update</button>
</th:block>
</td>
<td>
@ -247,7 +248,7 @@
<!-- Prev/Next Page Buttons -->
<button class="btn btn-success btn-xs" type="button" id="page-prev-btn"
style="margin-left: 15px;" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;">
style="margin-left: 15px;">
<span class="glyphicon glyphicon-step-backward"></span>
Prev Page
</button>
@ -257,15 +258,14 @@
}
$('#page-prev-btn').click(function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
btn.append($('<input />', { type: 'hidden', name: 'page-url', value: '<th:block th:if="${riBundle.getLink('prev') != null}" th:text="${riBundle.getLink('prev').url}"/>' }));
$("#outerForm").attr("action", "page").submit();
});
</script>
<button class="btn btn-success btn-xs" type="button" id="page-next-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;">
<button class="btn btn-success btn-xs" type="button" id="page-next-btn">
<span class="glyphicon glyphicon-step-forward"></span>
Next Page
</button>
@ -275,7 +275,7 @@
}
$('#page-next-btn').click(function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
btn.append($('<input />', { type: 'hidden', name: 'page-url', value: '<th:block th:if="${riBundle.getLink('next') != null}" th:text="${riBundle.getLink('next').url}"/>' }));
$("#outerForm").attr("action", "page").submit();
});
@ -303,8 +303,8 @@
<tr th:each="entry : ${riBundle.entry}">
<td style="white-space: nowrap;">
<th:block th:if="${entry.resource} != null">
<button class="btn btn-primary btn-xs" th:onclick="'readFromEntriesTable(this, \'' + ${entry.resource.idElement.resourceType} + '\', \'' + ${entry.resource.idElement.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.idElement.versionIdPart,'')} + '\');'" type="submit" name="action" value="read" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ><i class="fa fa-book"></i> Read</button>
<button class="btn btn-primary btn-xs" th:onclick="'updateFromEntriesTable(this, \'' + ${entry.resource.idElement.resourceType} + '\', \'' + ${entry.resource.idElement.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.idElement.versionIdPart, '')} + '\');'" type="submit" name="action" value="home"><i class="fa fa-pencil" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ></i> Update</button>
<button class="btn btn-primary btn-xs" th:onclick="'readFromEntriesTable(this, \'' + ${entry.resource.idElement.resourceType} + '\', \'' + ${entry.resource.idElement.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.idElement.versionIdPart,'')} + '\');'" type="submit" name="action" value="read"><i class="fa fa-book"></i> Read</button>
<button class="btn btn-primary btn-xs" th:onclick="'updateFromEntriesTable(this, \'' + ${entry.resource.idElement.resourceType} + '\', \'' + ${entry.resource.idElement.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.idElement.versionIdPart, '')} + '\');'" type="submit" name="action" value="home"><i class="fa fa-pencil"></i> Update</button>
</th:block>
</td>
<td>

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<script th:fragment="handler">
function handleActionButtonClick(button) {
// nothing for now
}
</script>
</html>

View File

@ -119,8 +119,8 @@
<h4>Resources</h4>
<ul class="nav nav-sidebar">
<th:block th:unless="${conf.rest.empty}" th:each="resource, resIterStat : ${conf.rest[0].resource}">
<ul class="nav nav-sidebar" th:unless="${conf.rest.empty}">
<th:block th:each="resource, resIterStat : ${conf.rest[0].resource}">
<li th:class="${resourceName} == ${resource.typeElement.valueAsString} ? 'active' : ''">
<a href="#" th:onclick="'doAction(this, \'resource\', \'' + ${resource.typeElement.valueAsString} + '\');'">
<th:block th:text="${resource.typeElement.valueAsString}" >Patient</th:block>

View File

@ -5,8 +5,7 @@
<div class="panel-heading">
<div class="pull-right">
<button type="button" th:id="'search-btn-' + ${queryIterStat.index}" class="btn btn-primary btn-block"
data-loading-text="Searching &lt;i class='fa fa-spinner fa-spin'/&gt;">
<button type="button" th:id="'search-btn-' + ${queryIterStat.index}" class="btn btn-primary btn-block">
&nbsp;<span class="glyphicon glyphicon-search"></span>
Execute
&nbsp;
@ -15,7 +14,7 @@
var searchButtonName = '<th:block th:text="'search-btn-' + ${queryIterStat.index}"/>';
$('#' + searchButtonName).click(function() {
var btn = $(this);
btn.button('loading');
handleActionButtonClick($(this));
var searchDiv = '<th:block th:text="'search-div-' + ${queryIterStat.index}"/>';
$('#' + searchDiv).find('input').each(function() {
if (this.id) {

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<title th:fragment="home">HAPI FHIR</title>
<title th:fragment="resource"><th:block th:text="${resourceName}"/> - HAPI FHIR</title>
<title th:fragment="result">Results - HAPI FHIR</title>
</html>

View File

@ -0,0 +1,121 @@
package ca.uhn.fhir.jpa.test;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
@Configuration
@EnableTransactionManagement()
public class FhirServerConfig extends BaseJavaConfigDstu2 {
/**
* Configure FHIR properties around the the JPA server via this bean
*/
@Bean()
public DaoConfig daoConfig() {
DaoConfig retVal = new DaoConfig();
retVal.setSubscriptionEnabled(true);
retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowMultipleDelete(true);
return retVal;
}
/**
* The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a
* directory called "jpaserver_derby_files".
*
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
*/
@Bean(destroyMethod = "close")
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver());
retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true");
retVal.setUsername("");
retVal.setPassword("");
return retVal;
}
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
retVal.setPersistenceUnitName("HAPI_PU");
retVal.setDataSource(dataSource());
retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity");
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
retVal.setJpaProperties(jpaProperties());
return retVal;
}
private Properties jpaProperties() {
Properties extraProperties = new Properties();
extraProperties.put("hibernate.dialect", org.hibernate.dialect.DerbyTenSevenDialect.class.getName());
extraProperties.put("hibernate.format_sql", "true");
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.jdbc.batch_size", "20");
extraProperties.put("hibernate.cache.use_query_cache", "false");
extraProperties.put("hibernate.cache.use_second_level_cache", "false");
extraProperties.put("hibernate.cache.use_structured_entries", "false");
extraProperties.put("hibernate.cache.use_minimal_puts", "false");
extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
return extraProperties;
}
/**
* Do some fancy logging to create a nice access log that has details about each incoming request.
*/
public IServerInterceptor loggingInterceptor() {
LoggingInterceptor retVal = new LoggingInterceptor();
retVal.setLoggerName("fhirtest.access");
retVal.setMessageFormat(
"Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]");
retVal.setLogExceptions(true);
retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}");
return retVal;
}
/**
* This interceptor adds some pretty syntax highlighting in responses when a browser is detected
*/
@Bean(autowire = Autowire.BY_TYPE)
public IServerInterceptor responseHighlighterInterceptor() {
ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IServerInterceptor subscriptionSecurityInterceptor() {
SubscriptionsRequireManualActivationInterceptorDstu2 retVal = new SubscriptionsRequireManualActivationInterceptorDstu2();
return retVal;
}
@Bean()
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);
return retVal;
}
}

View File

@ -1,15 +1,13 @@
package ca.uhn.fhir.jpa.test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ca.uhn.fhir.context.FhirContext;
@ -39,7 +37,7 @@ import ca.uhn.fhir.rest.server.RestfulServer;
public class OverlayTestApp {
private static ClassPathXmlApplicationContext ourAppCtx;
private static AnnotationConfigApplicationContext ourAppCtx;
@SuppressWarnings({ "unchecked" })
public static void main(String[] args) throws Exception {
@ -50,7 +48,7 @@ public class OverlayTestApp {
WebAppContext root = new WebAppContext();
root.setContextPath("/");
root.setDescriptor("src/main/webapp/WEB-INF/web.xml");
root.setDescriptor("src/test/resources/web.xml");
root.setResourceBase("src/main/webapp");
root.setParentLoaderPriority(true);
@ -61,10 +59,9 @@ public class OverlayTestApp {
}
ourAppCtx = new ClassPathXmlApplicationContext(
"hapi-fhir-server-resourceproviders-dstu2.xml",
"hapi-fhir-server-resourceproviders-dstu1.xml",
"fhir-jpabase-spring-test-config.xml");
if (true) {return;}
ourAppCtx = new AnnotationConfigApplicationContext(FhirServerConfig.class);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
@ -72,37 +69,37 @@ public class OverlayTestApp {
* DSTU2 resources
*/
RestfulServer restServerDev = new RestfulServer();
restServerDev.setPagingProvider(new FifoMemoryPagingProvider(10));
restServerDev.setImplementationDescription("This is a great server!!!!");
restServerDev.setFhirContext(ourAppCtx.getBean("myFhirContextDstu2", FhirContext.class));
RestfulServer restServerDstu2 = new RestfulServer();
restServerDstu2.setPagingProvider(new FifoMemoryPagingProvider(10));
restServerDstu2.setImplementationDescription("This is a great server!!!!");
restServerDstu2.setFhirContext(ourAppCtx.getBean("myFhirContextDstu2", FhirContext.class));
List<IResourceProvider> rpsDev = (List<IResourceProvider>) ourAppCtx.getBean("myResourceProvidersDstu2", List.class);
restServerDev.setResourceProviders(rpsDev);
restServerDstu2.setResourceProviders(rpsDev);
JpaSystemProviderDstu2 systemProvDev = (JpaSystemProviderDstu2) ourAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
restServerDev.setPlainProviders(systemProvDev);
restServerDstu2.setPlainProviders(systemProvDev);
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServerDev);
servletHolder.setServlet(restServerDstu2);
proxyHandler.addServlet(servletHolder, "/fhir/contextDstu2/*");
/*
* DSTU resources
*/
RestfulServer restServerDstu1 = new RestfulServer();
restServerDstu1.setPagingProvider(new FifoMemoryPagingProvider(10));
restServerDstu1.setImplementationDescription("This is a great server!!!!");
restServerDstu1.setFhirContext(ourAppCtx.getBean("myFhirContextDstu1", FhirContext.class));
List<IResourceProvider> rpsDstu1 = (List<IResourceProvider>) ourAppCtx.getBean("myResourceProvidersDstu1", List.class);
restServerDstu1.setResourceProviders(rpsDstu1);
JpaSystemProviderDstu1 systemProvDstu1 = (JpaSystemProviderDstu1) ourAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class);
restServerDstu1.setPlainProviders(systemProvDstu1);
servletHolder = new ServletHolder();
servletHolder.setServlet(restServerDstu1);
proxyHandler.addServlet(servletHolder, "/fhir/contextDstu1/*");
// RestfulServer restServerDstu1 = new RestfulServer();
// restServerDstu1.setPagingProvider(new FifoMemoryPagingProvider(10));
// restServerDstu1.setImplementationDescription("This is a great server!!!!");
// restServerDstu1.setFhirContext(ourAppCtx.getBean("myFhirContextDstu1", FhirContext.class));
// List<IResourceProvider> rpsDstu1 = (List<IResourceProvider>) ourAppCtx.getBean("myResourceProvidersDstu1", List.class);
// restServerDstu1.setResourceProviders(rpsDstu1);
//
// JpaSystemProviderDstu1 systemProvDstu1 = (JpaSystemProviderDstu1) ourAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class);
// restServerDstu1.setPlainProviders(systemProvDstu1);
//
// servletHolder = new ServletHolder();
// servletHolder.setServlet(restServerDstu1);
// proxyHandler.addServlet(servletHolder, "/fhir/contextDstu1/*");
int port = 8887;
Server server = new Server(port);
@ -115,7 +112,7 @@ public class OverlayTestApp {
if (true) {
String base = "http://localhost:" + port + "/fhir/contextDstu1";
IGenericClient client = restServerDstu1.getFhirContext().newRestfulGenericClient(base);
IGenericClient client = restServerDstu2.getFhirContext().newRestfulGenericClient(base);
client.setLogRequestAndResponse(true);
Organization o1 = new Organization();

View File

@ -0,0 +1,27 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<bean class="ca.uhn.fhir.to.TesterConfig">
<property name="servers">
<list>
<value>uhn , DSTU2 , UHN , http://fhirtest.uhn.ca/baseDstu2</value>
<value>home_d2 , DSTU2 , Localhost Server DSTU2 , http://localhost:8887/fhir/contextDstu2</value>
<value>hi , DSTU1 , Health Intersections , http://fhir.healthintersections.com.au/open</value>
<value>furore , DSTU1 , Spark - Furore Reference Server , http://spark.furore.com/fhir</value>
<value>blaze , DSTU1 , Blaze (Orion Health) , https://fhir.orionhealth.com/blaze/fhir</value>
<value>oridashi , DSTU1 , Oridashi , http://demo.oridashi.com.au:8190</value>
<value>fhirbase , DSTU1 , FHIRPlace (Health Samurai) , http://try-fhirplace.hospital-systems.com/ </value>
</list>
</property>
</bean>
<bean id="fhirContext" class="ca.uhn.fhir.context.FhirContext">
</bean>
</beans>

View File

@ -0,0 +1,44 @@
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/hapi-fhir-tester-application-context.xml
classpath:hapi-fhir-tester-config.xml
</param-value>
</context-param>
<!-- Processes application requests -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/hapi-fhir-tester-application-context.xml
classpath:hapi-fhir-tester-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
</web-app>

View File

@ -0,0 +1,27 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
<!-- Servlets -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -150,6 +150,14 @@
encoded correctly in XML, and sometimes not parsed correctly in JSON.
Thanks to Bill de Beaubien for reporting!
</action>
<action type="fix" issue="280">
The Web Testing UI has long had an issue where if you click on a button which
navigates to a new page (e.g. search, read, etc) and then click the back button
to return to the original page, the button you clicked remains disabled and can't
be clicked again (on Firefox and Safari). This is now fixed. Unfortunately the fix means that the
buttom will no longer show a "loading" spinner, but there doesn't seem to
be another way of fixing this. Thanks to Mark Scrimshire for reporting!
</action>
</release>
<release version="1.4" date="2016-02-04">
<action type="add">