Merge branch 'master' into hapi3_refactor
This commit is contained in:
commit
a92ace2e0d
|
@ -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
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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('"'));
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -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());
|
||||
|
||||
/*
|
||||
|
|
|
@ -43,7 +43,8 @@ public class BanUnsupportedHttpMethodsInterceptor extends InterceptorAdapter {
|
|||
myAllowedMethods.add(RequestTypeEnum.OPTIONS);
|
||||
myAllowedMethods.add(RequestTypeEnum.DELETE);
|
||||
myAllowedMethods.add(RequestTypeEnum.PUT);
|
||||
myAllowedMethods.add(RequestTypeEnum.POST);
|
||||
myAllowedMethods.add(RequestTypeEnum.POST);
|
||||
myAllowedMethods.add(RequestTypeEnum.PATCH);
|
||||
myAllowedMethods.add(RequestTypeEnum.HEAD);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,120 +62,165 @@ 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) {
|
||||
private boolean myShowRequestHeaders = false;
|
||||
private boolean myShowResponseHeaders = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ResponseHighlighterInterceptor() {
|
||||
super();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
return str;
|
||||
theTarget.append(str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
theTarget.append("<div id=\"line1\">");
|
||||
|
||||
if (theEncodingEnum == EncodingEnum.JSON) {
|
||||
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);
|
||||
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 (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) {
|
||||
|
||||
boolean inValue = false;
|
||||
boolean inQuote = false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char prevChar = (i > 0) ? str.charAt(i - 1) : ' ';
|
||||
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 (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'>"");
|
||||
theTarget.append("<span class='hlQuot'>"");
|
||||
} else {
|
||||
b.append("<span class='hlTagName'>"");
|
||||
theTarget.append("<span class='hlTagName'>"");
|
||||
}
|
||||
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) : ' ';
|
||||
} else {
|
||||
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'>></span>");
|
||||
theTarget.append("</span><span class='hlControl'>></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'>"");
|
||||
theTarget.append("<span class='hlQuot'>"");
|
||||
inQuote = true;
|
||||
i += 5;
|
||||
} else {
|
||||
b.append(nextChar);
|
||||
theTarget.append(nextChar);
|
||||
}
|
||||
} else {
|
||||
if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') {
|
||||
b.append("<span class='hlControl'><</span><span class='hlTagName'>");
|
||||
theTarget.append("<span class='hlControl'><</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,20 +544,74 @@ 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>");
|
||||
b.append(" </body>");
|
||||
// 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
|
||||
String out = b.toString();
|
||||
|
@ -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>");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
retVal.addName().addFamily("FAM");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
private IResource createPatient(Integer theId, int theVersion) {
|
||||
IResource retVal = createPatient(theId);
|
||||
retVal.setId(retVal.getId().withVersion(Integer.toString(theVersion)));
|
||||
|
@ -512,7 +512,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
HttpResponse status;
|
||||
|
||||
ourReturn = Arrays.asList(createPatient(2, 1));
|
||||
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -791,8 +791,8 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen()
|
||||
.build();
|
||||
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1101,7 +1101,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadByAnyId() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
|
@ -1216,8 +1216,8 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1230,7 +1230,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
for (int i = 0; i < 10; i++) {
|
||||
ourReturn.add(createPatient(1));
|
||||
}
|
||||
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -1242,9 +1242,9 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
assertEquals(10, respBundle.getTotal().intValue());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNotNull(respBundle.getLink("next"));
|
||||
|
||||
|
||||
// Load next page
|
||||
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -1265,8 +1265,8 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1282,7 +1282,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
for (int i = 0; i < 5; i++) {
|
||||
ourReturn.add(createPatient(2));
|
||||
}
|
||||
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -1294,9 +1294,9 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
assertEquals(10, respBundle.getTotal().intValue());
|
||||
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
|
||||
assertNotNull(respBundle.getLink("next"));
|
||||
|
||||
|
||||
// Load next page
|
||||
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
|
||||
status = ourClient.execute(httpGet);
|
||||
|
@ -1781,7 +1781,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
@Test
|
||||
public void testInvalidInstanceIds() throws Exception {
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance((String)null);
|
||||
new RuleBuilder().allow("Rule 1").write().instance((String) null);
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("theId must not be null or empty", e.getMessage());
|
||||
|
@ -1811,14 +1811,51 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
assertEquals("theId.getValue() must not be null or empty", e.getMessage());
|
||||
}
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance(new IdDt("Observation", (String)null));
|
||||
new RuleBuilder().allow("Rule 1").write().instance(new IdDt("Observation", (String) null));
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("theId must contain an ID part", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@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 {
|
||||
ourConditionalCreateId = "1";
|
||||
|
@ -1874,8 +1911,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
assertFalse(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@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 {
|
|||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,112 +21,154 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
*/
|
||||
public class PrePopulatedValidationSupport implements IValidationSupport {
|
||||
|
||||
private Map<String, StructureDefinition> myStructureDefinitions;
|
||||
private Map<String, ValueSet> myValueSets;
|
||||
private Map<String, CodeSystem> myCodeSystems;
|
||||
private Map<String, CodeSystem> myCodeSystems;
|
||||
private Map<String, StructureDefinition> myStructureDefinitions;
|
||||
private Map<String, ValueSet> myValueSets;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public PrePopulatedValidationSupport() {
|
||||
myStructureDefinitions = new HashMap<String,StructureDefinition>();
|
||||
myValueSets = new HashMap<String,ValueSet>();
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public PrePopulatedValidationSupport() {
|
||||
myStructureDefinitions = new HashMap<String, StructureDefinition>();
|
||||
myValueSets = new HashMap<String, ValueSet>();
|
||||
myCodeSystems = new HashMap<String, CodeSystem>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param theStructureDefinitions
|
||||
* The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and
|
||||
* values are the resource itself.
|
||||
* @param theValueSets
|
||||
* The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are
|
||||
* the resource itself.
|
||||
* @param theCodeSystems
|
||||
* The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are
|
||||
* the resource itself.
|
||||
*/
|
||||
public PrePopulatedValidationSupport(Map<String, StructureDefinition> theStructureDefinitions, Map<String, ValueSet> theValueSets, Map<String, CodeSystem> theCodeSystems) {
|
||||
myStructureDefinitions = theStructureDefinitions;
|
||||
myValueSets = theValueSets;
|
||||
myCodeSystems = theCodeSystems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theStructureDefinitions
|
||||
* The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and
|
||||
* values are the resource itself.
|
||||
* @param theValueSets
|
||||
* The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are
|
||||
* the resource itself.
|
||||
* @param theCodeSystems
|
||||
* The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are
|
||||
* the resource itself.
|
||||
*/
|
||||
public PrePopulatedValidationSupport(Map<String, StructureDefinition> theStructureDefinitions, Map<String, ValueSet> theValueSets, Map<String, CodeSystem> theCodeSystems) {
|
||||
myStructureDefinitions = theStructureDefinitions;
|
||||
myValueSets = theValueSets;
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
|
||||
return new ArrayList<StructureDefinition>(myStructureDefinitions.values());
|
||||
}
|
||||
private <T extends MetadataResource> void addToMap(T theStructureDefinition, Map<String, T> map, String theUrl) {
|
||||
if (isNotBlank(theUrl)) {
|
||||
map.put(theUrl, theStructureDefinition);
|
||||
|
||||
@Override
|
||||
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
|
||||
return myCodeSystems.get(theSystem);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
|
||||
if (theClass.equals(StructureDefinition.class)) {
|
||||
return (T) myStructureDefinitions.get(theUri);
|
||||
}
|
||||
if (theClass.equals(ValueSet.class)) {
|
||||
return (T) myValueSets.get(theUri);
|
||||
}
|
||||
if (theClass.equals(CodeSystem.class)) {
|
||||
return (T) myCodeSystems.get(theUri);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) {
|
||||
return myStructureDefinitions.get(theUrl);
|
||||
}
|
||||
/**
|
||||
* 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 boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
|
||||
return new ArrayList<StructureDefinition>(myStructureDefinitions.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
|
||||
return myCodeSystems.get(theSystem);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
|
||||
if (theClass.equals(StructureDefinition.class)) {
|
||||
return (T) myStructureDefinitions.get(theUri);
|
||||
}
|
||||
if (theClass.equals(ValueSet.class)) {
|
||||
return (T) myValueSets.get(theUri);
|
||||
}
|
||||
if (theClass.equals(CodeSystem.class)) {
|
||||
return (T) myCodeSystems.get(theUri);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) {
|
||||
return myStructureDefinitions.get(theUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
6
pom.xml
6
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -11,16 +11,16 @@
|
|||
|
||||
<a name="operations" />
|
||||
<p>
|
||||
This page shows the operations which can be implemented on
|
||||
This page shows the operations which can be implemented on
|
||||
HAPI
|
||||
<a href="./doc_rest_server.html">RESTful Servers</a>, as well as
|
||||
<a href="./doc_rest_client_annotation.html">Annotation Clients</a>.
|
||||
Most of the examples shown here show how to implement a server
|
||||
method, but to perform an equivalent call on an annotation
|
||||
client you simply put a method with the same signature in your
|
||||
client interface.
|
||||
client interface.
|
||||
</p>
|
||||
|
||||
|
||||
<a name="instance_read" />
|
||||
</section>
|
||||
|
||||
|
@ -80,7 +80,7 @@
|
|||
operation retrieves a specific version of a resource with a given ID.
|
||||
To support vread, simply add "version=true" to your @Read annotation. This
|
||||
means that the read method will support both "Read" and "VRead". The IdDt
|
||||
may or may not have the version populated depending on the client request.
|
||||
may or may not have the version populated depending on the client request.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
|
@ -93,7 +93,7 @@
|
|||
<br />
|
||||
<code>http://fhir.example.com/Patient/111/_history/2</code>
|
||||
</p>
|
||||
|
||||
|
||||
<a name="instance_update" />
|
||||
</section>
|
||||
|
||||
|
@ -156,14 +156,14 @@
|
|||
<param name="id" value="updateClient" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<h4>Conditional Updates</h4>
|
||||
<p>
|
||||
If you wish to suport conditional updates, you can add a parameter
|
||||
tagged with a
|
||||
tagged with a
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ConditionalOperationParam.html">@ConditionalOperationParam</a>
|
||||
annotation. If the request URL contains search parameters instead of a
|
||||
resource ID, then this parameter will be populated.
|
||||
resource ID, then this parameter will be populated.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
|
@ -185,7 +185,7 @@
|
|||
for any reason, you may also add parameters which have been annotated
|
||||
with the <code>@ResourceParam</code> of type
|
||||
<code>String</code> (to access the raw resource body) and/or
|
||||
<code>EncodingEnum</code> (to determine which encoding was used)
|
||||
<code>EncodingEnum</code> (to determine which encoding was used)
|
||||
</p>
|
||||
<p>
|
||||
The following example shows how to use these additonal data elements.
|
||||
|
@ -213,35 +213,35 @@
|
|||
server method should add the updated resource to the MethodOutcome object
|
||||
being returned, as shown in the example below.
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="updatePrefer" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<h4>Contention Aware Updating</h4>
|
||||
|
||||
<p>
|
||||
As of FHIR DSTU2, FHIR uses the <code>ETag</code> header to
|
||||
provide "conention aware updating". Under this scheme, a client
|
||||
may create a request that contains an ETag specifying the version,
|
||||
and the server will fail if the given version is not the latest
|
||||
and the server will fail if the given version is not the latest
|
||||
version.
|
||||
</p>
|
||||
<p>
|
||||
Such a request is shown below. In the following example, the update will
|
||||
only be applied if resource "Patient/123" is currently at version "3".
|
||||
Otherwise,
|
||||
Otherwise,
|
||||
</p>
|
||||
<pre><![CDATA[PUT [serverBase]/Patient/123
|
||||
If-Match: W/"3"]]></pre>
|
||||
|
||||
|
||||
<p>
|
||||
If a client performs a contention aware update, the ETag version will be
|
||||
placed in the version part of the IdDt/IdType that is passed into the
|
||||
placed in the version part of the IdDt/IdType that is passed into the
|
||||
method. For example:
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="updateEtag" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
|
@ -305,14 +305,14 @@ If-Match: W/"3"]]></pre>
|
|||
</p>
|
||||
|
||||
<h4>Conditional Deletes</h4>
|
||||
|
||||
|
||||
<p>
|
||||
The FHIR specification also allows "conditional deletes". A conditional
|
||||
delete uses a search style URL instead of a read style URL, and
|
||||
deletes a single resource if it matches the given search parameters.
|
||||
The following example shows how to
|
||||
The following example shows how to
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="deleteConditional" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
|
@ -382,7 +382,7 @@ If-Match: W/"3"]]></pre>
|
|||
</macro>
|
||||
|
||||
<h4>Conditional Creates</h4>
|
||||
|
||||
|
||||
<p>
|
||||
The FHIR specification also allows "conditional creates". A conditional
|
||||
create has an additional header called <code>If-None-Exist</code>
|
||||
|
@ -398,7 +398,7 @@ If-Match: W/"3"]]></pre>
|
|||
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ConditionalOperationParam.html">@ConditionalOperationParam</a>
|
||||
is detected, it will be populated with the value of this header.
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="createConditional" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
|
@ -409,21 +409,21 @@ If-Match: W/"3"]]></pre>
|
|||
<br />
|
||||
<code>http://fhir.example.com/Patient<br/>If-None-Exist: Patient?identifier=system%7C0001</code>
|
||||
</p>
|
||||
|
||||
|
||||
<h4>Prefer Header / Returning the resource body</h4>
|
||||
<p>
|
||||
If you wish to allow your server to honour the <code>Prefer</code>
|
||||
header, the same mechanism shown above for
|
||||
header, the same mechanism shown above for
|
||||
<a href="#prefer">Prefer Header for Updates</a> should be used.
|
||||
</p>
|
||||
|
||||
<h4>Accessing The Raw Resource Payload</h4>
|
||||
<p>
|
||||
The create operation also supports access to the raw payload,
|
||||
using the same semantics as raw payload access
|
||||
using the same semantics as raw payload access
|
||||
<a href="#raw_update_access">for the update operation</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<a name="type_search" />
|
||||
</section>
|
||||
|
||||
|
@ -482,9 +482,9 @@ If-Match: W/"3"]]></pre>
|
|||
individual HAPI resource
|
||||
classes.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
Parameters which take a string as their format should use the
|
||||
Parameters which take a string as their format should use the
|
||||
<code><a href="./apidocs/ca/uhn/fhir/rest/param/StringParam.html">StringParam</a></code>
|
||||
type. They may also use normal java <code>String</code>, although it is
|
||||
not possible to use the <code>:exact</code> qualifier in that case.
|
||||
|
@ -671,21 +671,21 @@ If-Match: W/"3"]]></pre>
|
|||
for patients.
|
||||
</p>
|
||||
<p>
|
||||
Reference parameters use the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/param/ReferenceParam.html">ReferenceParam</a>
|
||||
Reference parameters use the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/param/ReferenceParam.html">ReferenceParam</a>
|
||||
type. Reference parameters are, in their most basic form, just a pointer to another
|
||||
resource. For example, you might want to query for DiagnosticReport resources where the
|
||||
subject (the Patient resource that the report is about) is Patient/123. The following
|
||||
example shows a simple resource reference parameter in use.
|
||||
example shows a simple resource reference parameter in use.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="referenceSimple" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
|
||||
|
||||
<h4>Chained Resource References</h4>
|
||||
|
||||
|
||||
<p>
|
||||
References may also support a "chained" value. This is a search parameter name
|
||||
on the target resource. For example, you might want to search for DiagnosticReport
|
||||
|
@ -697,20 +697,20 @@ If-Match: W/"3"]]></pre>
|
|||
where the <b>subject</b> (Patient) of the report has the <b>family</b> (name) of
|
||||
'SMITH'</i>".
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
There are two ways of dealing with chained parameters in your methods: static chains and
|
||||
dynamic chains. Both are equally valid, although dyamic chains might lead to somewhat
|
||||
more compact and readable code.
|
||||
</p>
|
||||
|
||||
|
||||
<a name="dynamic_chains"/>
|
||||
<h4>Dynamic Chains</h4>
|
||||
|
||||
|
||||
<p>
|
||||
Chained values must be explicitly declared through the use
|
||||
of a whitelist (or blacklist). The following example shows how to declare a
|
||||
report with an allowable chained parameter:
|
||||
report with an allowable chained parameter:
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="referenceWithChain" />
|
||||
|
@ -720,7 +720,7 @@ If-Match: W/"3"]]></pre>
|
|||
<p>
|
||||
You may also specify the whitelist value of
|
||||
<code>""</code> to allow an empty chain (e.g. ther resource ID)
|
||||
and this can be combined with other values, as shown below:
|
||||
and this can be combined with other values, as shown below:
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
|
@ -738,7 +738,7 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="id" value="referenceWithDynamicChain" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<h4>Static Chains</h4>
|
||||
|
||||
<p>
|
||||
|
@ -766,11 +766,11 @@ If-Match: W/"3"]]></pre>
|
|||
<p>
|
||||
In the following example, Observation.name-value-date is shown. This parameter
|
||||
is a composite of a string and a date. Note that the composite parameter types
|
||||
(StringParam and DateParam) must be specified in both the annotation's
|
||||
(StringParam and DateParam) must be specified in both the annotation's
|
||||
<code>compositeTypes</code> field, as well as the generic types for the
|
||||
<code>CompositeParam</code> method parameter itself.
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="searchComposite" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
|
@ -810,23 +810,23 @@ If-Match: W/"3"]]></pre>
|
|||
|
||||
<p>
|
||||
If you wish to create a server that can accept any combination of a large number
|
||||
of parameters, (this is how the various reference servers behave, as well as the
|
||||
<a href="http://fhirtest.uhn.ca">public HAPI server</a>)
|
||||
of parameters, (this is how the various reference servers behave, as well as the
|
||||
<a href="http://fhirtest.uhn.ca">public HAPI server</a>)
|
||||
the easiest way to accomplish this is to simply create one method
|
||||
with all allowable parameters, each annotated as @OptionalParam.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
On the other hand, if you have specific combinations of parameters you wish to
|
||||
On the other hand, if you have specific combinations of parameters you wish to
|
||||
support (a common scenario if you are building FHIR on top of existing data sources
|
||||
and only have certain indexes you can use) you could create multiple search methods,
|
||||
each with specific required and optional parameters matching the database indexes.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
The following example shows a method with two parameters.
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="searchOptionalParam" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
|
@ -881,7 +881,7 @@ If-Match: W/"3"]]></pre>
|
|||
<p>
|
||||
It is worth noting that according to the FHIR specification, you can have an
|
||||
AND relationship combining multiple OR relationships, but not vice-versa. In
|
||||
other words, it's possible to support a search like
|
||||
other words, it's possible to support a search like
|
||||
<code>("name" = ("joe" or "john")) AND ("age" = (11 or 12))</code> but not
|
||||
a search like
|
||||
<code>("language" = ("en" AND "fr") OR ("address" = ("Canada" AND "Quebec"))</code>
|
||||
|
@ -892,7 +892,7 @@ If-Match: W/"3"]]></pre>
|
|||
<p>
|
||||
To accept a composite parameter, use a parameter type which implements the
|
||||
<a href="./apidocs/ca/uhn/fhir/model/api/IQueryParameterOr.html">IQueryParameterOr</a>
|
||||
interface.
|
||||
interface.
|
||||
</p>
|
||||
<p>
|
||||
Each parameter type (StringParam, TokenParam, etc.) has a corresponding parameter
|
||||
|
@ -924,7 +924,7 @@ If-Match: W/"3"]]></pre>
|
|||
interface (which in turn encapsulates the corresponding IQueryParameterOr types).
|
||||
</p>
|
||||
<p>
|
||||
An example follows which shows a search for Patients by address, where multiple string
|
||||
An example follows which shows a search for Patients by address, where multiple string
|
||||
lists may be supplied by the client. For example, the client might request that the
|
||||
address match <code>("Montreal" OR "Sherbrooke") AND ("Quebec" OR "QC")</code> using
|
||||
the following query:
|
||||
|
@ -932,11 +932,11 @@ If-Match: W/"3"]]></pre>
|
|||
<code>http://fhir.example.com/Patient?address=Montreal,Sherbrooke&address=Quebec,QC</code>
|
||||
</p>
|
||||
<p>
|
||||
The following code shows how to receive this parameter using a
|
||||
The following code shows how to receive this parameter using a
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/param/StringAndListParam.html">StringAndListParameter</a>,
|
||||
which can handle an AND list of multiple OR lists of strings.
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="searchMultipleAnd" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
|
@ -948,13 +948,13 @@ If-Match: W/"3"]]></pre>
|
|||
to use AND search parameters to specify a search criteria of
|
||||
<code>(A=1 OR A=2) AND (B=1 OR B=2)</code>
|
||||
but it is not possible to specify
|
||||
<code>(A=1 AND B=1) OR (A=2 AND B=2)</code> (aside from
|
||||
in very specific cases where a composite parameter has been
|
||||
specifically defined).
|
||||
<code>(A=1 AND B=1) OR (A=2 AND B=2)</code> (aside from
|
||||
in very specific cases where a composite parameter has been
|
||||
specifically defined).
|
||||
</p>
|
||||
|
||||
|
||||
<h4>AND Relationship Query Parameters for Dates</h4>
|
||||
|
||||
|
||||
<p>
|
||||
Dates are a bit of a special case, since it is a common scenario to want to match
|
||||
a date range (which is really just an AND query on two qualified date parameters).
|
||||
|
@ -1006,7 +1006,7 @@ If-Match: W/"3"]]></pre>
|
|||
|
||||
<p>
|
||||
To add support for reverse includes (via the <code>_revinclude</code> parameter),
|
||||
use the same format as with the <code>_include</code> parameter (shown above)
|
||||
use the same format as with the <code>_include</code> parameter (shown above)
|
||||
but add <code>reverse=true</code> to the <code>@IncludeParam</code>
|
||||
annotation, as shown below.
|
||||
</p>
|
||||
|
@ -1017,7 +1017,7 @@ If-Match: W/"3"]]></pre>
|
|||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
|
||||
<subsection name="Named Queries (_query)">
|
||||
|
||||
<p>
|
||||
|
@ -1055,15 +1055,15 @@ If-Match: W/"3"]]></pre>
|
|||
</p>
|
||||
|
||||
<p>
|
||||
According to the specification, sorting is requested by the client using a
|
||||
According to the specification, sorting is requested by the client using a
|
||||
search param as the sort key. For example, when searching Patient resources,
|
||||
a sort key of "given" requests the "given" search param as the sort key. That
|
||||
param maps to the values in the field "Patient.name.given".
|
||||
param maps to the values in the field "Patient.name.given".
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Sort specifications can be passed into handler methods by adding a parameter
|
||||
of type
|
||||
of type
|
||||
SortSpec,
|
||||
which has been annotated with the
|
||||
@Sort
|
||||
|
@ -1087,9 +1087,9 @@ If-Match: W/"3"]]></pre>
|
|||
|
||||
<p>
|
||||
It is also possible to annotate search methods and/or parameters with
|
||||
the
|
||||
the
|
||||
<a href="./apidocs/ca/uhn/fhir/model/api/annotation/Description.html">@Description</a>
|
||||
annotation. This annotation allows you to add a description of the method
|
||||
annotation. This annotation allows you to add a description of the method
|
||||
and the individual parameters. These descriptions will be placed in the
|
||||
server's conformance statement, which can be helpful to anyone who is developing
|
||||
software against your server.
|
||||
|
@ -1101,7 +1101,7 @@ If-Match: W/"3"]]></pre>
|
|||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
|
||||
<a name="type_validate" />
|
||||
</section>
|
||||
|
||||
|
@ -1124,7 +1124,7 @@ If-Match: W/"3"]]></pre>
|
|||
In FHIR DSTU1 the validate operation used a URL resembling <code>http://example.com/Patient/_validate</code>
|
||||
with a resource in the HTTP POST body. In FHIR DSTU2, validate has been changed to use the
|
||||
<a href="#extended_operations">extended operation</a> mechanism. It now uses a URL
|
||||
resembling <code>http://example.com/Patient/$validate</code> and takes a
|
||||
resembling <code>http://example.com/Patient/$validate</code> and takes a
|
||||
Parameters resource as input in the method body.<br/><br/>
|
||||
The mechanism described below may be used for both DSTU1 and DSTU2+ servers, and HAPI
|
||||
will automatically use the correct form depending on what FHIR version the
|
||||
|
@ -1180,13 +1180,13 @@ If-Match: W/"3"]]></pre>
|
|||
|
||||
<p>
|
||||
In the example above, only the <code>@ResourceParam</code> parameter is technically required, but
|
||||
in DSTU2 you are encouraged to also add the following parameters:
|
||||
in DSTU2 you are encouraged to also add the following parameters:
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>@Validate.Mode ValidationModeEnum mode</b> - This is the validation mode (see the FHIR specification for information on this)</li>
|
||||
<li><b>@Validate.Profile String profile</b> - This is the profile to validate against (see the FHIR specification for more information on this)</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<p>
|
||||
Example URL to invoke this method (this would be invoked using an HTTP POST,
|
||||
with a Parameters resource in the POST body):
|
||||
|
@ -1260,7 +1260,7 @@ If-Match: W/"3"]]></pre>
|
|||
<section name="System Level - Transaction">
|
||||
|
||||
<p>
|
||||
The
|
||||
The
|
||||
<a href="http://hl7.org/implement/standards/fhir/http.html#transaction">transaction</a>
|
||||
action is among the most challenging parts of the FHIR specification to implement. It allows the
|
||||
user to submit a bundle containing a number of resources to be created/updated/deleted as a single
|
||||
|
@ -1276,35 +1276,35 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="id" value="transaction" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
Transaction methods require one parameter annotated with @TransactionParam, and that
|
||||
parameter may be of type List<IResource> or Bundle.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
In terms of actually implementing the method, unfortunately there is only so much help
|
||||
HAPI will give you. One might expect HAPI to automatically delegate the individual
|
||||
operations in the transaction to other methods on the server but at this point it
|
||||
does not do that. There is a lot that transaction needs to handle
|
||||
(making everything atomic, replacing placeholder IDs across multiple resources
|
||||
which may even be circular, handling operations in the right order) and
|
||||
In terms of actually implementing the method, unfortunately there is only so much help
|
||||
HAPI will give you. One might expect HAPI to automatically delegate the individual
|
||||
operations in the transaction to other methods on the server but at this point it
|
||||
does not do that. There is a lot that transaction needs to handle
|
||||
(making everything atomic, replacing placeholder IDs across multiple resources
|
||||
which may even be circular, handling operations in the right order) and
|
||||
so far we have not found a way for the framework to do this in a generic way.
|
||||
</p>
|
||||
<p>
|
||||
What it comes down to is the fact that transaction is a tricky thing to implement.
|
||||
For what it's worth, you could look at our JPA module's "transaction" method in
|
||||
<a href="https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java">our source repository</a>
|
||||
<a href="https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java">our source repository</a>
|
||||
to see how we implemented transaction in the JPA server.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
Example URL to invoke this method:
|
||||
<br />
|
||||
<code>POST http://fhir.example.com/</code><br/>
|
||||
<i>(note that the content of this POST will be a bundle)</i>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<a name="system_search" />
|
||||
</section>
|
||||
|
@ -1414,13 +1414,13 @@ If-Match: W/"3"]]></pre>
|
|||
<u>
|
||||
<li>
|
||||
The <code>@Since</code> method argument implements the <code>_since</code>
|
||||
parameter and should be of type <code>DateTimeDt</code> or <code>DateTimeType</code>
|
||||
parameter and should be of type <code>DateTimeDt</code> or <code>DateTimeType</code>
|
||||
</li>
|
||||
<li>
|
||||
The <code>@At</code> method argument implements the <code>_at</code>
|
||||
parameter and may be of type
|
||||
parameter and may be of type
|
||||
<code>DateRangeParam</code>,
|
||||
<code>DateTimeDt</code> or <code>DateTimeType</code>
|
||||
<code>DateTimeDt</code> or <code>DateTimeType</code>
|
||||
</li>
|
||||
</u>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
||||
<!-- ****************************************************************** -->
|
||||
<!-- ****************************************************************** -->
|
||||
<!-- ****************************************************************** -->
|
||||
|
@ -1496,16 +1519,16 @@ If-Match: W/"3"]]></pre>
|
|||
<a href="http://hl7.org/implement/standards/fhir/extras.html#tags">here</a>
|
||||
before attempting to implement tagging in your own applications.
|
||||
</p>
|
||||
|
||||
|
||||
<subsection name="Accessing Tags in a Read / VRead / Search Method">
|
||||
|
||||
|
||||
<p>
|
||||
Tags are stored within a resource object, in the
|
||||
Tags are stored within a resource object, in the
|
||||
<a href="./apidocs/ca/uhn/fhir/model/api/IResource.html#getResourceMetadata()">IResource.html#getResourceMetadata()</a>
|
||||
map, under the key
|
||||
map, under the key
|
||||
<a href="./apidocs/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.html#TAG_LIST">TAG_LIST</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
In a server implementation, you may populate your tags into the
|
||||
returned resource(s) and HAPI will automatically place these tags into
|
||||
|
@ -1514,39 +1537,39 @@ If-Match: W/"3"]]></pre>
|
|||
example shows how to supply tags in a read method, but the same approach applies
|
||||
to vread and search operations as well.
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="readTags" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
In a client operation, you simply call the read/vread/search method as you
|
||||
normally would (as described above), and if any tags have been returned
|
||||
by the server, these may be accessed from the resource metadata.
|
||||
</p>
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="clientReadTags" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Setting Tags in a Create/Update Method">
|
||||
|
||||
|
||||
<p>
|
||||
Within a <a href="#type_create">Type Create</a>
|
||||
or <a href="#instance_update">Instance Update</a> method, it is
|
||||
possible for the client to specify a set of tags to be stored
|
||||
along with the saved resource instance.
|
||||
along with the saved resource instance.
|
||||
</p>
|
||||
<p>
|
||||
Note that FHIR specifies that in an update method, any tags supplied
|
||||
by the client are copied to the newly saved version, as well as any
|
||||
tags the existing version had.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
To work with tags in a create/update method, the pattern used in the
|
||||
read examples above is simply revered. In a server, the resource which
|
||||
|
@ -1557,16 +1580,16 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="id" value="createTags" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="More tag methods">
|
||||
|
||||
|
||||
<p>
|
||||
FHIR also provides a number of operations to interact directly
|
||||
with tags. These methods may be used to retrieve lists of tags
|
||||
that are available on the server, or to add or remove tags from
|
||||
resources without interacting directly with those resources.
|
||||
resources without interacting directly with those resources.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -1577,14 +1600,14 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="id" value="tagMethodProvider" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
On a client, the methods are defined in the exact same way, except that
|
||||
there is no method body in the client interface.
|
||||
</p>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ****************************************************************** -->
|
||||
|
@ -1592,18 +1615,18 @@ If-Match: W/"3"]]></pre>
|
|||
<!-- ****************************************************************** -->
|
||||
|
||||
<section name="_summary and _elements">
|
||||
|
||||
|
||||
The <code>_summary</code> and <code>_elements</code> parameters are
|
||||
automatically handled by the server, so no coding is required to make this
|
||||
work. If you wish to add parameters to manually handle these fields however,
|
||||
the following example shows how to access these.
|
||||
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="summaryAndElements" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="compartments" />
|
||||
</section>
|
||||
|
||||
|
@ -1619,7 +1642,7 @@ If-Match: W/"3"]]></pre>
|
|||
</p>
|
||||
<p>
|
||||
To define a search by compartment, you simply need to add the <code>compartmentName</code> attribute
|
||||
to the <code>@Search</code> annotation, and add an <code>@IdParam</code> parameter.
|
||||
to the <code>@Search</code> annotation, and add an <code>@IdParam</code> parameter.
|
||||
</p>
|
||||
<p>
|
||||
The following example shows a search method in a resource provider which returns
|
||||
|
@ -1630,40 +1653,40 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="id" value="searchCompartment" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
Example URL to invoke this method:
|
||||
<br />
|
||||
<code>http://fhir.example.com/Patient/123/Condition</code>
|
||||
</p>
|
||||
|
||||
|
||||
<a name="extended_operations"/>
|
||||
</section>
|
||||
|
||||
<section name="Extended Operations">
|
||||
|
||||
|
||||
<p>
|
||||
FHIR extended operations are a special type of RPC-style invocation you
|
||||
can perform against a FHIR server, type, or resource instance. These invocations
|
||||
take a
|
||||
take a
|
||||
<a href="./apidocs-dstu2/ca/uhn/fhir/model/dstu2/resource/Parameters.html">Parameters</a>
|
||||
resource as input, and return either another Parameters resource or a different resource type.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To define an operation, a method should be placed in a
|
||||
<a href="./doc_rest_server.html#resource_providers">Resource Provider</a>
|
||||
To define an operation, a method should be placed in a
|
||||
<a href="./doc_rest_server.html#resource_providers">Resource Provider</a>
|
||||
class if the operation works against a resource type (e.g. <code>Patient</code>)
|
||||
or a resource instance (e.g. <code>Patient/123</code>), or on a
|
||||
Plain Provider
|
||||
or a resource instance (e.g. <code>Patient/123</code>), or on a
|
||||
Plain Provider
|
||||
if the operation works against the server (i.e. it is global and not resource specific).
|
||||
</p>
|
||||
|
||||
<subsection name="Type-Specific Operations">
|
||||
|
||||
|
||||
<p>
|
||||
To implement a type-specific operation,
|
||||
the method should be annotated with the
|
||||
To implement a type-specific operation,
|
||||
the method should be annotated with the
|
||||
<code>@Operation</code> tag, and should have an
|
||||
<code>@OperationParam</code> tag for each named parameter that
|
||||
the input Parameters resource may be populated with. The following
|
||||
|
@ -1675,17 +1698,17 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="file"
|
||||
value="examples/src/main/java/example/ServerOperations.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
Example URL to invoke this operation (HTTP request body is Parameters resource):
|
||||
<br />
|
||||
<code>POST http://fhir.example.com/Patient/$everything</code>
|
||||
</p>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
|
||||
<subsection name="Instance-Specific Operations">
|
||||
|
||||
|
||||
<p>
|
||||
To create an instance-specific operation (an operation which takes the
|
||||
ID of a specific resource instance as a part of its request URL),
|
||||
|
@ -1698,25 +1721,25 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="file"
|
||||
value="examples/src/main/java/example/ServerOperations.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
Example URL to invoke this operation (HTTP request body is Parameters resource):
|
||||
<br />
|
||||
<code>http://fhir.example.com/Patient/123/$everything</code>
|
||||
</p>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Using Search Parameter Types">
|
||||
|
||||
|
||||
<p>
|
||||
FHIR allows operation parameters to be of a
|
||||
FHIR allows operation parameters to be of a
|
||||
<a href="http://hl7.org/fhir/search.html#ptypes">Search parameter type</a>
|
||||
(e.g. token) instead of a FHIR datatype (e.g. Coding).
|
||||
</p>
|
||||
<p>
|
||||
To use a search parameter type, any of the search parameter
|
||||
types listed in
|
||||
To use a search parameter type, any of the search parameter
|
||||
types listed in
|
||||
<a href="./doc_rest_operations.html#Type_Level_-_Search">Search</a>
|
||||
may be used. For example, the following is a simple operation method declaration
|
||||
using search parameters:
|
||||
|
@ -1726,7 +1749,7 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="file"
|
||||
value="examples/src/main/java/example/ServerOperations.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
Example URL to invoke this operation (HTTP request body is Parameters resource):
|
||||
<br />
|
||||
|
@ -1738,10 +1761,10 @@ If-Match: W/"3"]]></pre>
|
|||
if you want to be able to accept multiple values. For example,
|
||||
a <code>List<TokenParam></code> could be used if you want
|
||||
to allow multiple repetitions of a given token parameter (this is
|
||||
analogous to the "AND" semantics in a search).
|
||||
analogous to the "AND" semantics in a search).
|
||||
A <code>TokenOrListParam</code> could be used if you want to allow
|
||||
multiple values within a single repetition, separated by comma (this
|
||||
is analogous to "OR" semantics in a search).
|
||||
is analogous to "OR" semantics in a search).
|
||||
</p>
|
||||
<p>For example:</p>
|
||||
<macro name="snippet">
|
||||
|
@ -1749,14 +1772,14 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="file"
|
||||
value="examples/src/main/java/example/ServerOperations.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Server Operations">
|
||||
|
||||
<p>
|
||||
Server operations do not operate on a specific resource type or
|
||||
instance, but rather operate globally on the server itself. The following
|
||||
instance, but rather operate globally on the server itself. The following
|
||||
example show how to implement the
|
||||
<code>$closure</code> operation. Note that the <code>concept</code> parameter
|
||||
in the example has a cardinality of <code>0..*</code> according to the
|
||||
|
@ -1768,30 +1791,30 @@ If-Match: W/"3"]]></pre>
|
|||
<param name="file"
|
||||
value="examples/src/main/java/example/ServerOperations.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<p>
|
||||
Example URL to invoke this operation (HTTP request body is Parameters resource):
|
||||
<br />
|
||||
<code>http://fhir.example.com/$closure</code>
|
||||
</p>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Returning Multiple OUT Parameters">
|
||||
|
||||
|
||||
<p>
|
||||
In all of the Extended Operation examples above, the return
|
||||
type specified for the operation is a single Resource instance. This is
|
||||
a common pattern in FHIR defined operations. However, it is also
|
||||
a common pattern in FHIR defined operations. However, it is also
|
||||
possible for an extended operation to be defined with multiple
|
||||
and/or repeating OUT parameters. In this case, you can return
|
||||
a <code>Parameters</code> resource directly.
|
||||
a <code>Parameters</code> resource directly.
|
||||
</p>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Idempotent Operations / Handling HTTP Get">
|
||||
|
||||
|
||||
<p>
|
||||
The FHIR specification notes that if an operation is
|
||||
<a href="http://en.wikipedia.org/wiki/Idempotence">idempotent</a>
|
||||
|
@ -1801,7 +1824,7 @@ If-Match: W/"3"]]></pre>
|
|||
</p>
|
||||
<p>
|
||||
If you are implementing an operation which is idempotent,
|
||||
you should mark your operation with
|
||||
you should mark your operation with
|
||||
<code>idempotent=true</code>,
|
||||
as shown in some of the examples above. The default value
|
||||
for this flag is <code>false</code>, meaning that operations
|
||||
|
@ -1814,7 +1837,7 @@ If-Match: W/"3"]]></pre>
|
|||
server will respond with an <code>HTTP 405 Method Not Supported</code>.
|
||||
</p>
|
||||
</subsection>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
|
Loading…
Reference in New Issue