Merge branch 'master' into hapi3_refactor

This commit is contained in:
James Agnew 2017-07-13 12:02:27 -04:00
commit a92ace2e0d
17 changed files with 1317 additions and 443 deletions

View File

@ -0,0 +1,30 @@
package example;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.PatchTypeEnum;
public class PatchExamples {
//START SNIPPET: patch
@Patch
public OperationOutcome patientPatch(@IdParam IdType theId, PatchTypeEnum thePatchType, @ResourceParam String theBody) {
if (thePatchType == PatchTypeEnum.JSON_PATCH) {
// do something
}
if (thePatchType == PatchTypeEnum.XML_PATCH) {
// do something
}
OperationOutcome retVal = new OperationOutcome();
retVal.getText().setDivAsString("<div>OK</div>");
return retVal;
}
//END SNIPPET: patch
}

View File

@ -141,7 +141,7 @@ public class ServletExamples {
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
// Create the interceptor and register it
CorsInterceptor interceptor = new CorsInterceptor(config);

View File

@ -0,0 +1,88 @@
var selectedLines = new Array();
function updateHighlightedLine() {
updateHighlightedLineTo(window.location.hash);
}
function updateHighlightedLineTo(theNewHash) {
for (var next in selectedLines) {
document.getElementById('line' + selectedLines[next]).className = '';
document.getElementById('anchor' + selectedLines[next]).className = 'lineAnchor';
}
selectedLines = new Array();
var line = -1;
if (theNewHash && theNewHash.match('L[0-9]+-L[0-9]+')) {
var dashIndex = theNewHash.indexOf('-');
var start = parseInt(theNewHash.substring(2, dashIndex));
var end = parseInt(theNewHash.substring(dashIndex+2));
for (var i = start; i <= end; i++) {
selectedLines.push(i);
}
} else if (theNewHash && theNewHash.match('L[0-9]+')) {
var line = parseInt(theNewHash.substring(2));
selectedLines.push(line);
}
for (var next in selectedLines) {
// Prevent us from scrolling to the selected line
document.getElementById('L' + selectedLines[next]).name = '';
// Select the line number column
document.getElementById('line' + selectedLines[next]).className = 'selectedLine';
// Select the response body column
document.getElementById('anchor' + selectedLines[next]).className = 'lineAnchor selectedLine';
}
selectedLine = line;
}
(function() {
'use strict';
updateHighlightedLine();
window.onhashchange = updateHighlightedLine;
/* bail out if user is testing a version of this script via Greasemonkey or Tampermonkey */
if (window.HAPI_ResponseHighlighter_userscript) {
console.log("HAPI ResponseHighlighter: userscript detected - not executing embedded script");
return;
}
/* adds hyperlinks and CSS styles to dates and UUIDs (e.g. to enable user-select: all) */
const logicalReferenceRegex = /^[A-Z][A-Za-z]+\/[0-9]+$/;
const dateTimeRegex = /^-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00)))?)?)?$/; // from the spec - https://www.hl7.org/fhir/datatypes.html#datetime
const uuidRegex = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
const allQuotes = document.querySelectorAll(".hlQuot");
for (var i = 0; i < allQuotes.length; i++) {
const quote = allQuotes[i];
const text = quote.textContent.substr(1, quote.textContent.length - 2 /* remove quotes */);
const absHyperlink = text.startsWith("http://") || text.startsWith("https://");
const relHyperlink = text.match(logicalReferenceRegex);
const uuid = text.match(uuidRegex);
const dateTime = text.match(dateTimeRegex);
if (absHyperlink || relHyperlink) {
const link = document.createElement("a");
const href = absHyperlink ? text : "FHIR_BASE/" + text;
link.setAttribute("href", href);
link.textContent = '"' + text + '"';
quote.textContent = "";
quote.appendChild(link);
}
if (uuid || dateTime) {
const span = document.createElement("span");
span.setAttribute("class", uuid ? "uuid" : "dateTime");
span.textContent = text;
quote.textContent = "";
quote.appendChild(document.createTextNode('"'));
quote.appendChild(span);
quote.appendChild(document.createTextNode('"'));
}
}
})();

View File

@ -155,14 +155,18 @@ public class TestRestfulServer extends RestfulServer {
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
registerInterceptor(corsInterceptor);
/*
* We want to format the response using nice HTML if it's a browser, since this
* makes things a little easier for testers.
*/
registerInterceptor(new ResponseHighlighterInterceptor());
ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor();
responseHighlighterInterceptor.setShowRequestHeaders(false);
responseHighlighterInterceptor.setShowResponseHeaders(true);
registerInterceptor(responseHighlighterInterceptor);
registerInterceptor(new BanUnsupportedHttpMethodsInterceptor());
/*

View File

@ -44,6 +44,7 @@ public class BanUnsupportedHttpMethodsInterceptor extends InterceptorAdapter {
myAllowedMethods.add(RequestTypeEnum.DELETE);
myAllowedMethods.add(RequestTypeEnum.PUT);
myAllowedMethods.add(RequestTypeEnum.POST);
myAllowedMethods.add(RequestTypeEnum.PATCH);
myAllowedMethods.add(RequestTypeEnum.HEAD);
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -24,25 +25,24 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.io.InputStream;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.util.UrlUtil;
/**
@ -62,23 +62,64 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
* requesting _format=json or xml so eventually this parameter should be removed
*/
public static final String PARAM_RAW = "_raw";
public static final String PARAM_RAW_TRUE = "true";
public static final String PARAM_TRUE = "true";
private String format(String theResultBody, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
return str;
private boolean myShowRequestHeaders = false;
private boolean myShowResponseHeaders = true;
/**
* Constructor
*/
public ResponseHighlighterInterceptor() {
super();
}
StringBuilder b = new StringBuilder();
private String createLinkHref(Map<String, String[]> parameters, String formatValue) {
StringBuilder rawB = new StringBuilder();
for (String next : parameters.keySet()) {
if (Constants.PARAM_FORMAT.equals(next)) {
continue;
}
for (String nextValue : parameters.get(next)) {
if (isBlank(nextValue)) {
continue;
}
if (rawB.length() == 0) {
rawB.append('?');
} else {
rawB.append('&');
}
rawB.append(UrlUtil.escape(next));
rawB.append('=');
rawB.append(UrlUtil.escape(nextValue));
}
}
if (rawB.length() == 0) {
rawB.append('?');
} else {
rawB.append('&');
}
rawB.append(Constants.PARAM_FORMAT).append('=').append(formatValue);
if (theEncodingEnum == EncodingEnum.JSON) {
String link = rawB.toString();
return link;
}
private int format(String theResultBody, StringBuilder theTarget, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
theTarget.append(str);
return 0;
}
theTarget.append("<div id=\"line1\">");
boolean inValue = false;
boolean inQuote = false;
boolean inTag = false;
int lineCount = 1;
for (int i = 0; i < str.length(); i++) {
char prevChar = (i > 0) ? str.charAt(i - 1) : ' ';
char nextChar = str.charAt(i);
@ -87,95 +128,99 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' ';
char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' ';
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (nextChar == '\n') {
lineCount++;
theTarget.append("</div><div id=\"line");
theTarget.append(lineCount);
theTarget.append("\" onclick=\"updateHighlightedLineTo('#L");
theTarget.append(lineCount);
theTarget.append("');\">");
continue;
}
if (theEncodingEnum == EncodingEnum.JSON) {
if (inQuote) {
b.append(nextChar);
theTarget.append(nextChar);
if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;</span>");
theTarget.append("quot;</span>");
i += 5;
inQuote = false;
} else if (nextChar == '\\' && nextChar2 == '"') {
b.append("quot;</span>");
theTarget.append("quot;</span>");
i += 5;
inQuote = false;
}
} else {
if (nextChar == ':') {
inValue = true;
b.append(nextChar);
theTarget.append(nextChar);
} else if (nextChar == '[' || nextChar == '{') {
b.append("<span class='hlControl'>");
b.append(nextChar);
b.append("</span>");
theTarget.append("<span class='hlControl'>");
theTarget.append(nextChar);
theTarget.append("</span>");
inValue = false;
} else if (nextChar == '{' || nextChar == '}' || nextChar == ',') {
b.append("<span class='hlControl'>");
b.append(nextChar);
b.append("</span>");
theTarget.append("<span class='hlControl'>");
theTarget.append(nextChar);
theTarget.append("</span>");
inValue = false;
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
if (inValue) {
b.append("<span class='hlQuot'>&quot;");
theTarget.append("<span class='hlQuot'>&quot;");
} else {
b.append("<span class='hlTagName'>&quot;");
theTarget.append("<span class='hlTagName'>&quot;");
}
inQuote = true;
i += 5;
} else if (nextChar == ':') {
b.append("<span class='hlControl'>");
b.append(nextChar);
b.append("</span>");
theTarget.append("<span class='hlControl'>");
theTarget.append(nextChar);
theTarget.append("</span>");
inValue = true;
} else {
b.append(nextChar);
}
theTarget.append(nextChar);
}
}
} else {
boolean inQuote = false;
boolean inTag = false;
for (int i = 0; i < str.length(); i++) {
char nextChar = str.charAt(i);
char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' ';
char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' ';
char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' ';
char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' ';
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (inQuote) {
b.append(nextChar);
theTarget.append(nextChar);
if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;</span>");
theTarget.append("quot;</span>");
i += 5;
inQuote = false;
}
} else if (inTag) {
if (nextChar == '&' && nextChar2 == 'g' && nextChar3 == 't' && nextChar4 == ';') {
b.append("</span><span class='hlControl'>&gt;</span>");
theTarget.append("</span><span class='hlControl'>&gt;</span>");
inTag = false;
i += 3;
} else if (nextChar == ' ') {
b.append("</span><span class='hlAttr'>");
b.append(nextChar);
theTarget.append("</span><span class='hlAttr'>");
theTarget.append(nextChar);
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("<span class='hlQuot'>&quot;");
theTarget.append("<span class='hlQuot'>&quot;");
inQuote = true;
i += 5;
} else {
b.append(nextChar);
theTarget.append(nextChar);
}
} else {
if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') {
b.append("<span class='hlControl'>&lt;</span><span class='hlTagName'>");
theTarget.append("<span class='hlControl'>&lt;</span><span class='hlTagName'>");
inTag = true;
i += 3;
} else {
b.append(nextChar);
theTarget.append(nextChar);
}
}
}
}
return b.toString();
theTarget.append("</div>");
return lineCount;
}
@Override
@ -213,6 +258,22 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
return false;
}
/**
* If set to <code>true</code> (default is <code>false</code>) response will include the
* request headers
*/
public boolean isShowRequestHeaders() {
return myShowRequestHeaders;
}
/**
* If set to <code>true</code> (default is <code>true</code>) response will include the
* response headers
*/
public boolean isShowResponseHeaders() {
return myShowResponseHeaders;
}
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws AuthenticationException {
@ -283,7 +344,56 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
return false;
}
/**
* If set to <code>true</code> (default is <code>false</code>) response will include the
* request headers
*
* @return Returns a reference to this for easy method chaining
*/
public ResponseHighlighterInterceptor setShowRequestHeaders(boolean theShowRequestHeaders) {
myShowRequestHeaders = theShowRequestHeaders;
return this;
}
/**
* If set to <code>true</code> (default is <code>true</code>) response will include the
* response headers
*
* @return Returns a reference to this for easy method chaining
*/
public ResponseHighlighterInterceptor setShowResponseHeaders(boolean theShowResponseHeaders) {
myShowResponseHeaders = theShowResponseHeaders;
return this;
}
private void streamRequestHeaders(ServletRequest theServletRequest, StringBuilder b) {
if (theServletRequest instanceof HttpServletRequest) {
HttpServletRequest sr = (HttpServletRequest) theServletRequest;
b.append("<h1>Request</h1>");
b.append("<div class=\"headersDiv\">");
Enumeration<String> headerNamesEnum = sr.getHeaderNames();
while (headerNamesEnum.hasMoreElements()) {
String nextHeaderName = headerNamesEnum.nextElement();
Enumeration<String> headerValuesEnum = sr.getHeaders(nextHeaderName);
while (headerValuesEnum.hasMoreElements()) {
String nextHeaderValue = headerValuesEnum.nextElement();
b.append("<div class=\"headersRow\">");
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
}
}
b.append("</div>");
}
}
private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource, ServletRequest theServletRequest, int theStatusCode) {
if (theRequestDetails.getServer() instanceof RestfulServer) {
RestfulServer rs = (RestfulServer) theRequestDetails.getServer();
rs.addHeadersToResponse(theServletResponse);
}
IParser p;
Map<String, String[]> parameters = theRequestDetails.getParameters();
if (parameters.containsKey(Constants.PARAM_FORMAT)) {
@ -322,8 +432,18 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append(" <head>\n");
b.append(" <meta charset=\"utf-8\" />\n");
b.append(" <style>\n");
b.append(".hlQuot {\n");
b.append(" color: #88F;\n");
b.append(".httpStatusDiv {");
b.append(" font-size: 1.2em;");
b.append(" font-weight: bold;");
b.append("}");
b.append(".hlQuot { color: #88F; }\n");
b.append(".hlQuot a { text-decoration: underline; text-decoration-color: #CCC; }\n");
b.append(".hlQuot a:HOVER { text-decoration: underline; text-decoration-color: #008; }\n");
b.append(".hlQuot .uuid, .hlQuot .dateTime {\n");
b.append(" user-select: all;\n");
b.append(" -moz-user-select: all;\n");
b.append(" -webkit-user-select: all;\n");
b.append(" -ms-user-select: element;\n");
b.append("}\n");
b.append(".hlAttr {\n");
b.append(" color: #888;\n");
@ -340,7 +460,12 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append(".hlUrlBase {\n");
b.append("}");
b.append(".headersDiv {\n");
b.append(" background: #EEE;");
b.append(" padding: 10px;");
b.append(" margin-left: 10px;");
b.append(" border: 1px solid #CCC;");
b.append(" border-radius: 10px;");
b.append("}");
b.append(".headersRow {\n");
b.append("}");
b.append(".headerName {\n");
b.append(" color: #888;\n");
@ -350,6 +475,32 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append(" color: #88F;\n");
b.append(" font-family: monospace;\n");
b.append("}");
b.append(".responseBodyTable {");
b.append(" width: 100%;");
b.append(" margin-left: 0px;");
b.append(" margin-top: 20px;");
b.append("}");
b.append(".responseBodyTableFirstColumn {");
b.append("}");
b.append(".responseBodyTableSecondColumn {");
b.append(" width: 100%;");
b.append("}");
b.append(".lineAnchor A {");
b.append(" text-decoration: none;");
b.append(" padding-left: 20px;");
b.append("}");
b.append(".lineAnchor {");
b.append(" display: block;");
b.append(" padding-right: 20px;");
b.append("}");
b.append(".selectedLine {");
b.append(" background-color: #EEF;");
b.append(" font-weight: bold;");
b.append("}");
b.append("H1 {");
b.append(" font-size: 1.1em;");
b.append(" color: #666;");
b.append("}");
b.append("BODY {\n");
b.append(" font-family: Arial;\n");
b.append("}");
@ -393,19 +544,73 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append("\n");
// if (isEncodeHeaders()) {
// b.append("<h1>Request Headers</h1>");
// b.append("<div class=\"headersDiv\">");
// for (int next : theRequestDetails.get)
// b.append("</div>");
// b.append("<h1>Response Headers</h1>");
// b.append("<div class=\"headersDiv\">");
// b.append("</div>");
// b.append("<h1>Response Body</h1>");
// }
b.append("<pre>");
b.append(format(encoded, encoding));
b.append("</pre>");
// status (e.g. HTTP 200 OK)
String statusName = Constants.HTTP_STATUS_NAMES.get(theServletResponse.getStatus());
statusName = defaultString(statusName);
b.append("<div class=\"httpStatusDiv\">");
b.append("HTTP ");
b.append(theServletResponse.getStatus());
b.append(" ");
b.append(statusName);
b.append("</div>");
b.append("\n");
b.append("\n");
if (true) {
try {
if (isShowRequestHeaders()) {
streamRequestHeaders(theServletRequest, b);
}
if (isShowResponseHeaders()) {
streamResponseHeaders(theRequestDetails, theServletResponse, b);
}
} catch (Throwable t) {
// ignore (this will hit if we're running in a servlet 2.5 environment)
}
}
StringBuilder target = new StringBuilder();
int linesCount = format(encoded, target, encoding);
b.append("<table class=\"responseBodyTable\" cellspacing=\"0\">");
b.append("<tr>");
b.append("<td class=\"responseBodyTableFirstColumn\"><pre>");
for (int i = 1; i <= linesCount; i++) {
b.append("<div class=\"lineAnchor\" id=\"anchor");
b.append(i);
b.append("\">");
b.append("<a href=\"#L");
b.append(i);
b.append("\" name=\"L");
b.append(i);
b.append("\" id=\"L");
b.append(i);
b.append("\">");
b.append(i);
b.append("</a></div>");
}
b.append("</pre></td>");
// Response Body
b.append("<td class=\"responseBodyTableSecondColumn\"><pre>");
b.append(target);
b.append("</pre></td>");
b.append("</tr>");
b.append("</table>");
b.append("\n");
InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js");
String jsStr = jsStream != null ? IOUtils.toString(jsStream, "UTF-8") : "console.log('ResponseHighlighterInterceptor: javascript resource not found')";
jsStr = jsStr.replace("FHIR_BASE", theRequestDetails.getServerBaseForRequest());
b.append("<script type=\"text/javascript\">");
b.append(jsStr);
b.append("</script>\n");
b.append("</body>");
b.append("</html>");
//@formatter:off
@ -419,35 +624,31 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
}
}
private String createLinkHref(Map<String, String[]> parameters, String formatValue) {
StringBuilder rawB = new StringBuilder();
for (String next : parameters.keySet()) {
if (Constants.PARAM_FORMAT.equals(next)) {
continue;
}
for (String nextValue : parameters.get(next)) {
if (isBlank(nextValue)) {
continue;
}
if (rawB.length() == 0) {
rawB.append('?');
} else {
rawB.append('&');
}
rawB.append(UrlUtil.escape(next));
rawB.append('=');
rawB.append(UrlUtil.escape(nextValue));
}
}
if (rawB.length() == 0) {
rawB.append('?');
} else {
rawB.append('&');
}
rawB.append(Constants.PARAM_FORMAT).append('=').append(formatValue);
private void streamResponseHeaders(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, StringBuilder b) {
if (theServletResponse.getHeaderNames().isEmpty() == false) {
b.append("<h1>Response</h1>");
String link = rawB.toString();
return link;
b.append("<div class=\"headersDiv\">");
for (String nextHeaderName : theServletResponse.getHeaderNames()) {
for (String nextHeaderValue : theServletResponse.getHeaders(nextHeaderName)) {
/*
* Let's pretend we're returning a FHIR content type even though we're
* actually returning an HTML one
*/
if (nextHeaderName.equalsIgnoreCase(Constants.HEADER_CONTENT_TYPE)) {
ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, theRequestDetails.getServer().getDefaultResponseEncoding());
if (responseEncoding != null && isNotBlank(responseEncoding.getResourceContentType())) {
nextHeaderValue = responseEncoding.getResourceContentType() + ";charset=utf-8";
}
}
b.append("<div class=\"headersRow\">");
b.append("<span class=\"headerName\">").append(nextHeaderName).append(": ").append("</span>");
b.append("<span class=\"headerValue\">").append(nextHeaderValue).append("</span>");
b.append("</div>");
}
}
b.append("</div>");
}
}
}

View File

@ -154,6 +154,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
case CREATE:
case UPDATE:
case PATCH:
// if (theRequestResource != null) {
// if (theRequestResource.getIdElement() != null) {
// if (theRequestResource.getIdElement().hasIdPart() == false) {

View File

@ -42,14 +42,13 @@
<optional>true</optional>
</dependency>
<!-- The JPA project uses a newer API but we'll try to hold to this version as much as possible. See #283. -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- Testing -->
<!-- <dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-structures-dstu</artifactId> <version>0.8</version> <scope>test</scope> </dependency> -->
<dependency>

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
@ -32,8 +33,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.*;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@ -53,14 +53,14 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*;
public class ResponseHighlightingInterceptorTest {
private static ResponseHighlighterInterceptor ourInterceptor = new ResponseHighlighterInterceptor();
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseHighlightingInterceptorTest.class);
@ -74,6 +74,12 @@ public class ResponseHighlightingInterceptorTest {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Before
public void before() {
ourInterceptor.setShowRequestHeaders(new ResponseHighlighterInterceptor().isShowRequestHeaders());
ourInterceptor.setShowResponseHeaders(new ResponseHighlighterInterceptor().isShowResponseHeaders());
}
/**
* See #464
*/
@ -89,7 +95,7 @@ public class ResponseHighlightingInterceptorTest {
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (stringContainsInOrder("<body>", "<pre>", "\n", "</pre>")));
assertThat(responseContent, (stringContainsInOrder("<body>", "<pre>", "<div", "</pre>")));
}
/**
@ -107,7 +113,7 @@ public class ResponseHighlightingInterceptorTest {
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (stringContainsInOrder("<body>", "<pre>", "\n", "</pre>")));
assertThat(responseContent, (stringContainsInOrder("<body>", "<pre>", "<div", "</pre>")));
}
/**
@ -142,6 +148,73 @@ public class ResponseHighlightingInterceptorTest {
}
@Test
public void testShowNeither() throws Exception {
ourInterceptor.setShowRequestHeaders(false);
ourInterceptor.setShowResponseHeaders(false);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, not(containsStringIgnoringCase("Accept")));
assertThat(responseContent, not(containsStringIgnoringCase("Content-Type")));
}
@Test
public void testShowResponse() throws Exception {
ourInterceptor.setShowResponseHeaders(true);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, not(containsStringIgnoringCase("Accept")));
assertThat(responseContent, (containsStringIgnoringCase("Content-Type")));
}
@Test
public void testShowRequest() throws Exception {
ourInterceptor.setShowRequestHeaders(true);
ourInterceptor.setShowResponseHeaders(false);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, (containsStringIgnoringCase("Accept")));
assertThat(responseContent, not(containsStringIgnoringCase("Content-Type")));
}
@Test
public void testShowRequestAndResponse() throws Exception {
ourInterceptor.setShowRequestHeaders(true);
ourInterceptor.setShowResponseHeaders(true);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=html/json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html;charset=utf-8", status.getFirstHeader("content-type").getValue().replace(" ", "").toLowerCase());
assertThat(responseContent, (containsStringIgnoringCase("Accept")));
assertThat(responseContent, (containsStringIgnoringCase("Content-Type")));
}
@Test
public void testGetInvalidResource() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Foobar/123");
@ -190,7 +263,7 @@ public class ResponseHighlightingInterceptorTest {
@Test
public void testHighlightException() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -207,7 +280,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
reqDetails.setServer(new RestfulServer(ourCtx));
reqDetails.setServletRequest(req);
@ -228,7 +301,7 @@ public class ResponseHighlightingInterceptorTest {
@Test
public void testHighlightNormalResponseForcePrettyPrint() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -245,7 +318,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
HashMap<String, String[]> params = new HashMap<String, String[]>();
params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE });
@ -258,12 +331,12 @@ public class ResponseHighlightingInterceptorTest {
String output = sw.getBuffer().toString();
ourLog.info(output);
assertThat(output, containsString("<span class='hlTagName'>Patient</span>"));
assertThat(output, stringContainsInOrder("<body>", "<pre>", "\n", "</pre>"));
assertThat(output, stringContainsInOrder("<body>", "<pre>", "<div", "</pre>"));
}
@Test
public void testHighlightForceRaw() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -280,7 +353,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
HashMap<String, String[]> params = new HashMap<String, String[]>();
params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE });
@ -297,7 +370,7 @@ public class ResponseHighlightingInterceptorTest {
@Test
public void testDontHighlightWhenOriginHeaderPresent() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -320,7 +393,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
HashMap<String, String[]> params = new HashMap<String, String[]>();
reqDetails.setParameters(params);
@ -337,7 +410,7 @@ public class ResponseHighlightingInterceptorTest {
*/
@Test
public void testHighlightForceHtmlCt() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -354,7 +427,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
HashMap<String, String[]> params = new HashMap<String, String[]>();
params.put(Constants.PARAM_FORMAT, new String[] { Constants.FORMAT_HTML });
@ -371,7 +444,7 @@ public class ResponseHighlightingInterceptorTest {
*/
@Test
public void testHighlightForceHtmlFormat() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -388,7 +461,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
HashMap<String, String[]> params = new HashMap<String, String[]>();
params.put(Constants.PARAM_FORMAT, new String[] { Constants.CT_HTML });
@ -402,7 +475,7 @@ public class ResponseHighlightingInterceptorTest {
@Test
public void testHighlightNormalResponse() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@ -419,7 +492,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
reqDetails.setParameters(new HashMap<String, String[]>());
reqDetails.setServer(new RestfulServer(ourCtx));
@ -430,7 +503,7 @@ public class ResponseHighlightingInterceptorTest {
String output = sw.getBuffer().toString();
ourLog.info(output);
assertThat(output, containsString("<span class='hlTagName'>Patient</span>"));
assertThat(output, stringContainsInOrder("<body>", "<pre>", "\n", "</pre>"));
assertThat(output, stringContainsInOrder("<body>", "<pre>", "<div", "</pre>"));
assertThat(output, containsString("<a href=\"?_format=json\">"));
}
@ -439,7 +512,7 @@ public class ResponseHighlightingInterceptorTest {
*/
@Test
public void testHighlightProducesDefaultJsonWithBrowserRequest() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
@ -457,7 +530,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
reqDetails.setParameters(new HashMap<String, String[]>());
RestfulServer server = new RestfulServer(ourCtx);
@ -477,7 +550,7 @@ public class ResponseHighlightingInterceptorTest {
@Test
public void testHighlightProducesDefaultJsonWithBrowserRequest2() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
ResponseHighlighterInterceptor ic = ourInterceptor;
HttpServletRequest req = mock(HttpServletRequest.class);
@ -495,7 +568,7 @@ public class ResponseHighlightingInterceptorTest {
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
ServletRequestDetails reqDetails = new ServletRequestDetails();
ServletRequestDetails reqDetails = new TestServletRequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
reqDetails.setParameters(new HashMap<String, String[]>());
RestfulServer server = new RestfulServer(ourCtx);
@ -712,7 +785,7 @@ public class ResponseHighlightingInterceptorTest {
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
ourServlet.registerInterceptor(corsInterceptor);
ourServlet.registerInterceptor(new ResponseHighlighterInterceptor());
ourServlet.registerInterceptor(ourInterceptor);
ourServlet.setResourceProviders(patientProvider, new DummyBinaryResourceProvider());
ourServlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(ourServlet);
@ -756,9 +829,6 @@ public class ResponseHighlightingInterceptorTest {
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyPatientResourceProvider implements IResourceProvider {
private Patient createPatient1() {
@ -861,4 +931,11 @@ public class ResponseHighlightingInterceptorTest {
}
class TestServletRequestDetails extends ServletRequestDetails {
@Override
public String getServerBaseForRequest() {
return "/baseDstu3";
}
}
}

View File

@ -1818,6 +1818,43 @@ public class AuthorizationInterceptorDstu2Test {
}
}
@Test
public void testWritePatchByInstance() throws Exception {
ourConditionalCreateId = "1";
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 1").write().instance("Patient/900").andThen()
.build();
//@formatter:on
}
});
HttpEntityEnclosingRequestBase httpPost;
HttpResponse status;
String response;
String input = "[ { \"op\": \"replace\", \"path\": \"/gender\", \"value\": \"male\" } ]";
ourHitMethod = false;
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/900");
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals(204, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999");
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testWriteByInstance() throws Exception {
@ -1875,7 +1912,6 @@ public class AuthorizationInterceptorDstu2Test {
}
@Test
public void testReadByInstance() throws Exception {
ourConditionalCreateId = "1";
@ -1923,7 +1959,6 @@ public class AuthorizationInterceptorDstu2Test {
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
@ -2110,7 +2145,6 @@ public class AuthorizationInterceptorDstu2Test {
return retVal;
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
@ -2184,6 +2218,14 @@ public class AuthorizationInterceptorDstu2Test {
return retVal;
}
@Patch()
public MethodOutcome patch(@IdParam IdDt theId, @ResourceParam String theResource, PatchTypeEnum thePatchType) {
ourHitMethod = true;
MethodOutcome retVal = new MethodOutcome();
return retVal;
}
@Validate
public MethodOutcome validate(@ResourceParam Patient theResource, @IdParam IdDt theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding,
@Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile, RequestDetails theRequestDetails) {
@ -2226,5 +2268,4 @@ public class AuthorizationInterceptorDstu2Test {
}
}

View File

@ -7,6 +7,7 @@ import java.io.InputStreamReader;
import java.util.*;
import org.apache.commons.io.Charsets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
@ -22,6 +23,10 @@ import ca.uhn.fhir.context.FhirContext;
public class DefaultProfileValidationSupport implements IValidationSupport {
private static final String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class);
private Map<String, CodeSystem> myCodeSystems;
@ -86,51 +91,28 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
Validate.notBlank(theUri, "theUri must not be null or blank");
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
if (theClass.equals(StructureDefinition.class)) {
return (T) fetchStructureDefinition(theContext, theUri);
}
if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) {
return (T) fetchValueSet(theContext, theUri);
}
// if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
// Map<String, ValueSet> defaultValueSets = myDefaultValueSets;
// if (defaultValueSets == null) {
// String path = theContext.getVersion().getPathToSchemaDefinitions().replace("/schema", "/valueset") + "/valuesets.xml";
// InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(path);
// if (valuesetText == null) {
// return null;
// }
// InputStreamReader reader;
// try {
// reader = new InputStreamReader(valuesetText, "UTF-8");
// } catch (UnsupportedEncodingException e) {
// // Shouldn't happen!
// throw new InternalErrorException("UTF-8 encoding not supported on this platform", e);
// }
//
// defaultValueSets = new HashMap<String, ValueSet>();
//
// Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
// for (BundleEntryComponent next : bundle.getEntry()) {
// IdType nextId = new IdType(next.getFullUrl());
// if (nextId.isEmpty() || !nextId.getValue().startsWith("http://hl7.org/fhir/ValueSet/")) {
// continue;
// }
// defaultValueSets.put(nextId.toVersionless().getValue(), (ValueSet) next.getResource());
// }
//
// myDefaultValueSets = defaultValueSets;
// }
//
// return (T) defaultValueSets.get(theUri);
// }
return null;
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) {
return provideStructureDefinitionMap(theContext).get(theUrl);
String url = theUrl;
if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
// no change
} else if (url.indexOf('/') == -1) {
url = URL_PREFIX_STRUCTURE_DEFINITION + url;
} else if (StringUtils.countMatches(url, '/') == 1) {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
}
return provideStructureDefinitionMap(theContext).get(url);
}
ValueSet fetchValueSet(FhirContext theContext, String theSystem) {

View File

@ -1,14 +1,14 @@
package org.hl7.fhir.dstu3.hapi.validation;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -21,9 +21,9 @@ import ca.uhn.fhir.context.FhirContext;
*/
public class PrePopulatedValidationSupport implements IValidationSupport {
private Map<String, CodeSystem> myCodeSystems;
private Map<String, StructureDefinition> myStructureDefinitions;
private Map<String, ValueSet> myValueSets;
private Map<String, CodeSystem> myCodeSystems;
/**
* Constructor
@ -34,37 +34,6 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
myCodeSystems = new HashMap<String, CodeSystem>();
}
/**
* Add a new StructureDefinition resource which will be available to the validator. Note that
* {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this
* value will be used as the logical URL.
*/
public void addStructureDefinition(StructureDefinition theStructureDefinition) {
Validate.notBlank(theStructureDefinition.getUrl(), "theStructureDefinition.getUrl() must not return a value");
myStructureDefinitions.put(theStructureDefinition.getUrl(), theStructureDefinition);
}
/**
* Add a new ValueSet resource which will be available to the validator. Note that
* {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this
* value will be used as the logical URL.
*/
public void addValueSet(ValueSet theValueSet) {
Validate.notBlank(theValueSet.getUrl(), "theValueSet.getUrl() must not return a value");
myValueSets.put(theValueSet.getUrl(), theValueSet);
}
/**
* Add a new CodeSystem resource which will be available to the validator. Note that
* {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this
* value will be used as the logical URL.
*/
public void addCodeSystem(CodeSystem theCodeSystem) {
Validate.notBlank(theCodeSystem.getUrl(), "theCodeSystem.getUrl() must not return a value");
myCodeSystems.put(theCodeSystem.getUrl(), theCodeSystem);
}
/**
* Constructor
*
@ -84,6 +53,79 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
myCodeSystems = theCodeSystems;
}
/**
* Add a new CodeSystem resource which will be available to the validator. Note that
* {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this
* value will be used as the logical URL.
* <p>
* Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
* it will be stored in three ways:
* <ul>
* <li>Extension</li>
* <li>StructureDefinition/Extension</li>
* <li>http://hl7.org/StructureDefinition/Extension</li>
* </ul>
* </p>
*/
public void addCodeSystem(CodeSystem theCodeSystem) {
Validate.notBlank(theCodeSystem.getUrl(), "theCodeSystem.getUrl() must not return a value");
addToMap(theCodeSystem, myCodeSystems, theCodeSystem.getUrl());
}
/**
* Add a new StructureDefinition resource which will be available to the validator. Note that
* {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this
* value will be used as the logical URL.
* <p>
* Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
* it will be stored in three ways:
* <ul>
* <li>Extension</li>
* <li>StructureDefinition/Extension</li>
* <li>http://hl7.org/StructureDefinition/Extension</li>
* </ul>
* </p>
*/
public void addStructureDefinition(StructureDefinition theStructureDefinition) {
Validate.notBlank(theStructureDefinition.getUrl(), "theStructureDefinition.getUrl() must not return a value");
addToMap(theStructureDefinition, myStructureDefinitions, theStructureDefinition.getUrl());
}
private <T extends MetadataResource> void addToMap(T theStructureDefinition, Map<String, T> map, String theUrl) {
if (isNotBlank(theUrl)) {
map.put(theUrl, theStructureDefinition);
int lastSlashIdx = theUrl.lastIndexOf('/');
if (lastSlashIdx != -1) {
map.put(theUrl.substring(lastSlashIdx + 1), theStructureDefinition);
int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1);
if (previousSlashIdx != -1) {
map.put(theUrl.substring(previousSlashIdx + 1), theStructureDefinition);
}
}
}
}
/**
* Add a new ValueSet resource which will be available to the validator. Note that
* {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this
* value will be used as the logical URL.
* <p>
* Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
* it will be stored in three ways:
* <ul>
* <li>Extension</li>
* <li>StructureDefinition/Extension</li>
* <li>http://hl7.org/StructureDefinition/Extension</li>
* </ul>
* </p>
*/
public void addValueSet(ValueSet theValueSet) {
Validate.notBlank(theValueSet.getUrl(), "theValueSet.getUrl() must not return a value");
addToMap(theValueSet, myValueSets, theValueSet.getUrl());
}
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
return null;

View File

@ -0,0 +1,34 @@
package org.hl7.fhir.dstu3.hapi.validation;
import static org.junit.Assert.*;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
public class DefaultProfileValidationSupportTest {
private DefaultProfileValidationSupport mySvc = new DefaultProfileValidationSupport();
private static FhirContext ourCtx = FhirContext.forDstu3();
@Test
public void testGetStructureDefinitionsWithRelativeUrls() {
assertNotNull(mySvc.fetchStructureDefinition(ourCtx, "http://hl7.org/fhir/StructureDefinition/Extension"));
assertNotNull(mySvc.fetchStructureDefinition(ourCtx, "StructureDefinition/Extension"));
assertNotNull(mySvc.fetchStructureDefinition(ourCtx, "Extension"));
assertNull(mySvc.fetchStructureDefinition(ourCtx, "http://hl7.org/fhir/StructureDefinition/Extension2"));
assertNull(mySvc.fetchStructureDefinition(ourCtx, "StructureDefinition/Extension2"));
assertNull(mySvc.fetchStructureDefinition(ourCtx, "Extension2"));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,322 @@
package org.hl7.fhir.dstu3.hapi.validation;
import static org.junit.Assert.assertEquals;
import java.util.*;
import javax.annotation.Nullable;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.utils.StructureMapUtilities;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
public class StructureMapTest {
/**
* The logger object.
*/
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StructureMapTest.class);
/**
* The basic fhir context used to parse structure definitions.
*/
FhirContext context;
/**
* HapiFhirContext used when building strucutre map utilities.
*/
IWorkerContext hapiContext;
/**
* path to the files used to test the profile generator.
*/
String resourcePath = null;
/**
* Used to validate definitions as well as add new structure definitions to a registry.
*/
PrePopulatedValidationSupport validationSupport;
public StructureMap.StructureMapGroupRuleSourceComponent buildSource(String context, @Nullable String element, @Nullable String variable, @Nullable String type, @Nullable Integer min,
@Nullable String max) {
StructureMap.StructureMapGroupRuleSourceComponent retVal = new StructureMap.StructureMapGroupRuleSourceComponent();
retVal.setContext(context);
if (element != null)
retVal.setElement(element);
if (variable != null)
retVal.setVariable(variable);
if (type != null)
retVal.setType(type);
if (min != null)
retVal.setMin(min);
if (max != null)
retVal.setMax(max);
return retVal;
}
public List<StructureMap.StructureMapGroupRuleSourceComponent> buildSourceList(StructureMap.StructureMapGroupRuleSourceComponent[] sources) {
List<StructureMap.StructureMapGroupRuleSourceComponent> retVal = new ArrayList<StructureMap.StructureMapGroupRuleSourceComponent>();
retVal.addAll(Arrays.asList(sources));
return retVal;
}
public StructureMap.StructureMapGroupRuleTargetComponent buildTarget(@Nullable String context, @Nullable String element, @Nullable String variable,
@Nullable StructureMap.StructureMapTransform transform, @Nullable TargetParam[] params) throws Exception {
StructureMap.StructureMapGroupRuleTargetComponent retVal = new StructureMap.StructureMapGroupRuleTargetComponent();
if (context != null)
retVal.setContext(context);
if (element != null)
retVal.setElement(element);
if (variable != null)
retVal.setVariable(variable);
if (transform != null)
retVal.setTransform(transform);
if (params != null) {
if (params.length > 0)
retVal.setParameter(this.constructParameters(params));
}
return retVal;
}
public List<StructureMap.StructureMapGroupRuleTargetComponent> buildTargetList(StructureMap.StructureMapGroupRuleTargetComponent[] sources) {
List<StructureMap.StructureMapGroupRuleTargetComponent> retVal = new ArrayList<StructureMap.StructureMapGroupRuleTargetComponent>();
retVal.addAll(Arrays.asList(sources));
return retVal;
}
public List<StructureMap.StructureMapGroupComponent> buildTestGroup() throws Exception {
List<StructureMap.StructureMapGroupComponent> retVal = new ArrayList<StructureMap.StructureMapGroupComponent>();
StructureMap.StructureMapGroupComponent group = new StructureMap.StructureMapGroupComponent();
group.setName("TestStructureToCoding");
group.setTypeMode(StructureMap.StructureMapGroupTypeMode.TYPEANDTYPES);
group.setInput(this.buildTestInput());
group.setRule(this.buildTestRules());
retVal.add(group);
return retVal;
}
public List<StructureMap.StructureMapGroupInputComponent> buildTestInput() {
List<StructureMap.StructureMapGroupInputComponent> retVal = new ArrayList<StructureMap.StructureMapGroupInputComponent>();
StructureMap.StructureMapGroupInputComponent sourceIn = new StructureMap.StructureMapGroupInputComponent();
StructureMap.StructureMapGroupInputComponent targetIn = new StructureMap.StructureMapGroupInputComponent();
sourceIn.setName("source");
sourceIn.setType("TestStructure");
sourceIn.setMode(StructureMap.StructureMapInputMode.SOURCE);
targetIn.setName("target");
targetIn.setType("http://hl7.org/fhir/StructureDefinition/Coding");
targetIn.setMode(StructureMap.StructureMapInputMode.TARGET);
retVal.add(sourceIn);
retVal.add(targetIn);
return retVal;
}
public List<StructureMap.StructureMapGroupRuleComponent> buildTestRules() throws Exception {
List<StructureMap.StructureMapGroupRuleComponent> retVal = new ArrayList<StructureMap.StructureMapGroupRuleComponent>();
StructureMap.StructureMapGroupRuleComponent codingSystem = new StructureMap.StructureMapGroupRuleComponent();
StructureMap.StructureMapGroupRuleComponent codingExtension = new StructureMap.StructureMapGroupRuleComponent();
codingSystem.setName("Coding.System");
codingSystem.setSource(this.buildSourceList(new StructureMap.StructureMapGroupRuleSourceComponent[] {
this.buildSource("source", "system", "v", null, null, null)
}));
codingSystem.setTarget(this.buildTargetList(new StructureMap.StructureMapGroupRuleTargetComponent[] {
this.buildTarget("target", "system", null, StructureMap.StructureMapTransform.COPY, new TargetParam[] { new TargetParam("Id", "v") })
}));
codingExtension.setName("Coding.Extension");
codingExtension.setSource(this.buildSourceList(new StructureMap.StructureMapGroupRuleSourceComponent[] {
this.buildSource("source", "system", "v", null, null, null)
}));
codingExtension.setTarget(this.buildTargetList(new StructureMap.StructureMapGroupRuleTargetComponent[] {
this.buildTarget("target", "extension", "ex", null, new TargetParam[] { new TargetParam("", "") }),
this.buildTarget("ex", "url", null, StructureMap.StructureMapTransform.COPY, new TargetParam[] { new TargetParam("Id", "v") }),
this.buildTarget("ex", "value", null, StructureMap.StructureMapTransform.COPY, new TargetParam[] { new TargetParam("String", "v") })
}));
retVal.add(codingSystem);
retVal.add(codingExtension);
return retVal;
}
public List<StructureMap.StructureMapGroupRuleTargetParameterComponent> constructParameters(TargetParam[] params) throws Exception {
List<StructureMap.StructureMapGroupRuleTargetParameterComponent> parameterComponents = new ArrayList<StructureMap.StructureMapGroupRuleTargetParameterComponent>();
for (TargetParam tp : params) {
if (tp.getType() == "Id") // TODO: Convert TypeParam.Type into an Enum.
parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new IdType().setValue(tp.getValue())));
else if (tp.getType() == "String")
parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue((new StringType().setValue(tp.getValue()))));
else if (tp.getType() == "Boolean") {
boolean bValue = Boolean.getBoolean(tp.getValue());
parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new BooleanType().setValue(bValue)));
} else if (tp.getType() == "Integer") {
int iValue = Integer.getInteger(tp.getValue());
parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new IntegerType().setValue(iValue)));
} else if (tp.getType() == "Decimal") {
long lValue = Long.getLong(tp.getValue());
parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new DecimalType(lValue)));
}
}
return parameterComponents;
}
public List<StructureMap.StructureMapStructureComponent> createMapStructureList() {
List<StructureMap.StructureMapStructureComponent> retVal = new ArrayList<StructureMap.StructureMapStructureComponent>();
StructureMap.StructureMapStructureComponent source = new StructureMap.StructureMapStructureComponent();
StructureMap.StructureMapStructureComponent target = new StructureMap.StructureMapStructureComponent();
source.setUrl("http://opencimi.org/structuredefinition/TestStructure");
source.setMode(StructureMap.StructureMapModelMode.SOURCE);
target.setUrl("http://hl7.org/fhir/StructureDefinition/Coding");
target.setMode(StructureMap.StructureMapModelMode.TARGET);
retVal.add(source);
retVal.add(target);
return retVal;
}
public StructureDefinition.StructureDefinitionDifferentialComponent createTestDiff() {
StructureDefinition.StructureDefinitionDifferentialComponent retVal = new StructureDefinition.StructureDefinitionDifferentialComponent();
List<ElementDefinition> eList = new ArrayList<ElementDefinition>();
ElementDefinition ed0 = new ElementDefinition();
// ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent();
// base.setId("http://hl7.org/fhir/StructureDefinition/Element");
ed0.setId("TestStructure");
ed0.setSliceName("TestStructure");
ed0.setPath("TestStructure");
// ed0.setBase(base);
ed0.setMin(1);
ed0.setMax("1");
eList.add(ed0);
ElementDefinition ed = new ElementDefinition();
// ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent();
// base.setId("http://hl7.org/fhir/StructureDefinition/Element");
ed.setId("system");
ed.setSliceName("system");
ed.setPath("TestStructure.system");
// ed.setBase(base);
ed.setFixed(new UriType().setValue("HTTP://opencimi.org/structuredefinition/TestStructure.html#Debugging"));
// ed.setType(this.createTypeRefList());
eList.add(ed);
retVal.setElement(eList);
return retVal;
}
public StructureDefinition.StructureDefinitionSnapshotComponent createTestSnapshot() {
StructureDefinition.StructureDefinitionSnapshotComponent retVal = new StructureDefinition.StructureDefinitionSnapshotComponent();
List<ElementDefinition> eList = new ArrayList<ElementDefinition>();
ElementDefinition ed0 = new ElementDefinition();
// ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent();
// base.setId("http://hl7.org/fhir/StructureDefinition/Element");
ed0.setId("TestStructure");
ed0.setSliceName("TestStructure");
ed0.setPath("TestStructure");
// ed0.setBase(base);
ed0.setMin(1);
ed0.setMax("1");
eList.add(ed0);
ElementDefinition ed = new ElementDefinition();
// ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent();
// base.setId("http://hl7.org/fhir/StructureDefinition/Element");
ed.setId("system");
ed.setSliceName("system");
ed.setPath("TestStructure.system");
// ed.setBase(base);
ed.setFixed(new UriType().setValue("HTTP://opencimi.org/structuredefinition/TestStructure.html#Debugging"));
// ed.setType(this.createTypeRefList());
ed.setMin(1);
ed.setMax("1");
eList.add(ed);
retVal.setElement(eList);
return retVal;
}
public StructureDefinition createTestStructure() {
StructureDefinition sd = new StructureDefinition();
sd.setId("TestStructure");
sd.setUrl("http://opencimi.org/structuredefinition/TestStructure");
sd.setStatus(Enumerations.PublicationStatus.DRAFT);
sd.setName("TestStructure");
sd.setType("TestStructure");
sd.setSnapshot(this.createTestSnapshot());
sd.setDifferential(this.createTestDiff());
sd.setKind(StructureDefinition.StructureDefinitionKind.LOGICAL);
return sd;
}
public StructureMap createTestStructuremap() throws Exception {
StructureMap retMap = new StructureMap();
retMap.setUrl("http://opencimi.org/structuremap/testtransform");
retMap.setName("TestTransform");
retMap.setStatus(Enumerations.PublicationStatus.DRAFT);
retMap.setStructure(this.createMapStructureList());
retMap.setGroup(this.buildTestGroup());
return retMap;
}
/**
* Sets up the resource paths as well as create the contexts using a defalut validator to start with.
*
* @throws Exception
*/
@Before
public void setUp() throws Exception {
if (this.context == null) {
this.context = FhirContext.forDstu3();
}
}
/**
* See #682
*/
@Test
public void testMappingTransform() throws Exception {
Map<String, StructureMap> maps = new HashMap<String, StructureMap>(); // Instantiate a hashmap for StructureMaps
this.validationSupport = new PrePopulatedValidationSupport(); // Create Validation Instance
for (StructureDefinition sd : new DefaultProfileValidationSupport().fetchAllStructureDefinitions(this.context)) { // Read in the default Structure Definitions into a validator that allows custom
// declared structure definitions.
this.validationSupport.addStructureDefinition(sd);
}
StructureDefinition sd1 = this.createTestStructure(); // Calls a method that constructs a comp
this.validationSupport.addStructureDefinition(sd1); // Add custom structure to validation support.
this.hapiContext = new HapiWorkerContext(this.context, this.validationSupport);// Init the Hapi Work
StructureMap map = this.createTestStructuremap();
maps.put(map.getUrl(), map);
StructureMapUtilities scu = new StructureMapUtilities(hapiContext, maps, null, null);
List<StructureDefinition> result = scu.analyse(null, map).getProfiles();
assertEquals(1, result.size());
ourLog.info(context.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.get(0)));
}
public class TargetParam {
private String type;
private String value;
public TargetParam(String type, String value) {
this.type = type;
this.value = value;
}
public String getType() {
return type;
}
public String getValue() {
return value;
}
public void setType(String type) {
this.type = type;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -47,7 +47,7 @@
</snapshots>
<id>bintray-dnault-maven</id>
<name>bintray</name>
<url>http://dl.bintray.com/dnault/maven</url>
<url>https://dl.bintray.com/dnault/maven</url>
</repository>
<repository>
<id>jitpack.io</id>
@ -337,6 +337,10 @@
<id>ohr</id>
<name>Christian Ohr</name>
</developer>
<developer>
<id>eug48</id>
<name>Eugene Lubarsky</name>
</developer>
</developers>
<licenses>

View File

@ -161,6 +161,31 @@
XML encoding because of the browser Accept header even though
this was not what the client wanted.
</action>
<action type="add" issue="651">
Enhancement to ResponseHighlighterInterceptor where links in the resource
body are now converted to actual clickable hyperlinks. Thanks to Eugene Lubarsky
for the pull request!
</action>
<action type="add">
BanUnsupportedHttpMethodsInterceptor has been modified so that it now allows
HTTP PATCH to proceed.
</action>
<action type="add" issue="651">
Enhancement to ResponseHighlighterInterceptor so that it now can be configured
to display the request headers and response headers, and individual lines
may be highlighted.
</action>
<action type="fix">
AuthorizationInterceptor did not permit PATCH operations to proceed even
if the user had write access for the resource being patched.
</action>
<action type="fix" issue="682">
Fix an issue in HapiWorkerContext where structure definitions are
not able to be retrieved if they are referred to by their
relative or logical ID. This affects profile tooling such as
StructureMapUtilities. Thanks to Travis Lukach for reporting and
providing a test case!
</action>
</release>
<release version="2.5" date="2017-06-08">
<action type="fix">

View File

@ -1441,6 +1441,29 @@ If-Match: W/"3"]]></pre>
<a name="exceptions" />
</section>
<section name="Instance Level - Patch">
<p>
HAPI FHIR includes basic support for the
<a href="http://hl7.org/implement/standards/fhir/http.html#patch">
<b>patch</b>
</a>
operation. This support allows you to perform patches, but does not
include logic to actually implement resource patching in the server
framework (note that the JPA server does include a patch implementation).
</p>
<p>
The following snippet shows how to define a patch method on a server:
</p>
<macro name="snippet">
<param name="id" value="patch" />
<param name="file" value="examples/src/main/java/example/PatchExamples.java" />
</macro>
</patch>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->