
ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor Maven / Gradle / Ivy
package ca.uhn.fhir.rest.server.interceptor;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.RequestTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
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.util.UrlUtil;
/**
* This interceptor detects when a request is coming from a browser, and automatically returns a response with syntax
* highlighted (coloured) HTML for the response instead of just returning raw XML/JSON.
*
* @since 1.0
*/
public class ResponseHighlighterInterceptor extends InterceptorAdapter {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseHighlighterInterceptor.class);
private static final String[] PARAM_FORMAT_VALUE_JSON = new String[] { Constants.FORMAT_JSON };
private static final String[] PARAM_FORMAT_VALUE_XML = new String[] { Constants.FORMAT_XML };
/**
* TODO: As of HAPI 1.6 (2016-06-10) this parameter has been replaced with simply
* requesting _format=json or xml so eventually this parameter should be removed
*/
public static final String PARAM_RAW = "_raw";
public static final String PARAM_RAW_TRUE = "true";
public static final String PARAM_TRUE = "true";
private String format(String theResultBody, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
return str;
}
StringBuilder b = new StringBuilder();
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);
if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;");
i += 5;
inQuote = false;
} else if (nextChar == '\\' && nextChar2 == '"') {
b.append("quot;");
i += 5;
inQuote = false;
}
} else {
if (nextChar == ':') {
inValue = true;
b.append(nextChar);
} else if (nextChar == '[' || nextChar == '{') {
b.append("");
b.append(nextChar);
b.append("");
inValue = false;
} else if (nextChar == '}' || nextChar == '}' || nextChar == ',') {
b.append("");
b.append(nextChar);
b.append("");
inValue = false;
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
if (inValue) {
b.append(""");
} else {
b.append(""");
}
inQuote = true;
i += 5;
} else if (nextChar == ':') {
b.append("");
b.append(nextChar);
b.append("");
inValue = true;
} else {
b.append(nextChar);
}
}
}
} else {
boolean inQuote = false;
boolean inTag = false;
for (int i = 0; i < str.length(); i++) {
char nextChar = str.charAt(i);
char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' ';
char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' ';
char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' ';
char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' ';
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (inQuote) {
b.append(nextChar);
if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;");
i += 5;
inQuote = false;
}
} else if (inTag) {
if (nextChar == '&' && nextChar2 == 'g' && nextChar3 == 't' && nextChar4 == ';') {
b.append(">");
inTag = false;
i += 3;
} else if (nextChar == ' ') {
b.append("");
b.append(nextChar);
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append(""");
inQuote = true;
i += 5;
} else {
b.append(nextChar);
}
} else {
if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') {
b.append("<");
inTag = true;
i += 3;
} else {
b.append(nextChar);
}
}
}
}
return b.toString();
}
@Override
public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException, IOException {
/*
* It's not a browser...
*/
Set accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theServletRequest);
if (!accept.contains(Constants.CT_HTML)) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
/*
* It's an AJAX request, so no HTML
*/
String requestedWith = theServletRequest.getHeader("X-Requested-With");
if (requestedWith != null) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
/*
* Not a GET
*/
if (theRequestDetails.getRequestType() != RequestTypeEnum.GET) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
if (theException.getOperationOutcome() == null) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
streamResponse(theRequestDetails, theServletResponse, theException.getOperationOutcome(), theServletRequest);
return false;
}
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
/*
* Request for _raw
*/
String[] rawParamValues = theRequestDetails.getParameters().get(PARAM_RAW);
if (rawParamValues != null && rawParamValues.length > 0 && rawParamValues[0].equals(PARAM_RAW_TRUE)) {
ourLog.warn("Client is using non-standard/legacy _raw parameter - Use _format=json or _format=xml instead, as this parmameter will be removed at some point");
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
boolean force = false;
String[] formatParams = theRequestDetails.getParameters().get(Constants.PARAM_FORMAT);
if (formatParams != null && formatParams.length > 0) {
String formatParam = formatParams[0];
if (Constants.FORMATS_HTML.contains(formatParam)) { // this is a set
force = true;
} else if (Constants.FORMATS_HTML_XML.equals(formatParam)) {
force = true;
theRequestDetails.getParameters().put(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_XML);
} else if (Constants.FORMATS_HTML_JSON.equals(formatParam)) {
force = true;
theRequestDetails.getParameters().put(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_JSON);
} else {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
}
/*
* It's not a browser...
*/
Set highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theServletRequest);
if (!force && highestRankedAcceptValues.contains(Constants.CT_HTML) == false) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
/*
* It's an AJAX request, so no HTML
*/
if (!force && isNotBlank(theServletRequest.getHeader("X-Requested-With"))) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
/*
* If the request has an Origin header, it is probably an AJAX request
*/
if (!force && isNotBlank(theServletRequest.getHeader(Constants.HEADER_ORIGIN))) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
/*
* Not a GET
*/
if (!force && theRequestDetails.getRequestType() != RequestTypeEnum.GET) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
/*
* Not binary
*/
if (!force && "Binary".equals(theRequestDetails.getResourceName())) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
streamResponse(theRequestDetails, theServletResponse, theResponseObject, theServletRequest);
return false;
}
private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource, ServletRequest theServletRequest) {
IParser p;
Map parameters = theRequestDetails.getParameters();
if (parameters.containsKey(Constants.PARAM_FORMAT)) {
p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), theRequestDetails);
} else {
EncodingEnum defaultResponseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding();
p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext());
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
}
// This interceptor defaults to pretty printing unless the user
// has specifically requested us not to
boolean prettyPrintResponse = true;
String[] prettyParams = parameters.get(Constants.PARAM_PRETTY);
if (prettyParams != null && prettyParams.length > 0) {
if (Constants.PARAM_PRETTY_VALUE_FALSE.equals(prettyParams[0])) {
prettyPrintResponse = false;
}
}
if (prettyPrintResponse) {
p.setPrettyPrint(prettyPrintResponse);
}
EncodingEnum encoding = p.getEncoding();
String encoded = p.encodeResourceToString(resource);
theServletResponse.setContentType(Constants.CT_HTML_WITH_UTF8);
StringBuilder b = new StringBuilder();
b.append("\n");
b.append(" \n");
b.append(" \n");
b.append(" \n");
b.append(" \n");
b.append("\n");
b.append(" ");
b.append("");
b.append("This result is being rendered in HTML for easy viewing. ");
b.append("You may access this content as ");
b.append("Raw JSON or ");
b.append("Raw XML, ");
b.append(" or view this content in ");
b.append("HTML JSON ");
b.append("or ");
b.append("HTML XML.");
Date startTime = (Date) theServletRequest.getAttribute(RestfulServer.REQUEST_START_TIME);
if (startTime != null) {
long time = System.currentTimeMillis() - startTime.getTime();
b.append(" Response generated in ");
b.append(time);
b.append("ms.");
}
b.append("
");
b.append("\n");
// if (isEncodeHeaders()) {
// b.append("Request Headers
");
// b.append("");
// for (int next : theRequestDetails.get)
// b.append("");
// b.append("Response Headers
");
// b.append("");
// b.append("");
// b.append("Response Body
");
// }
b.append("");
b.append(format(encoded, encoding));
b.append("
");
b.append(" ");
b.append("");
//@formatter:off
String out = b.toString();
//@formatter:on
try {
theServletResponse.getWriter().append(out);
theServletResponse.getWriter().close();
} catch (IOException e) {
throw new InternalErrorException(e);
}
}
private String createLinkHref(Map 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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy