All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor Maven / Gradle / Ivy

There is a newer version: 7.6.1
Show newest version
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