package ca.uhn.fhir.rest.server; /* * #%L * HAPI FHIR - Core Library * %% * Copyright (C) 2014 - 2017 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 static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.URLEncoder; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.method.ElementsParameter; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SummaryEnumParameter; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.DateUtils; public class RestfulServerUtils { static final Pattern ACCEPT_HEADER_PATTERN = Pattern.compile("\\s*([a-zA-Z0-9+.*/-]+)\\s*(;\\s*([a-zA-Z]+)\\s*=\\s*([a-zA-Z0-9.]+)\\s*)?(,?)"); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class); private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<String>(Arrays.asList("Bundle", "*.text", "*.(mandatory)")); public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) { // Pretty print boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails); parser.setPrettyPrint(prettyPrint); parser.setServerBaseUrl(theRequestDetails.getFhirServerBase()); // Summary mode Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails); // _elements Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails); if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) { throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters"); } Set<String> elementsAppliesTo = null; if (elements != null && isNotBlank(theRequestDetails.getResourceName())) { elementsAppliesTo = Collections.singleton(theRequestDetails.getResourceName()); } if (summaryMode != null) { if (summaryMode.contains(SummaryEnum.COUNT)) { parser.setEncodeElements(Collections.singleton("Bundle.total")); } else if (summaryMode.contains(SummaryEnum.TEXT)) { parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); } else { parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA)); parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE)); } } if (elements != null && elements.size() > 0) { Set<String> newElements = new HashSet<String>(); for (String next : elements) { newElements.add("*." + next); } parser.setEncodeElements(newElements); parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo); } } public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint, BundleTypeEnum theBundleType) { try { StringBuilder b = new StringBuilder(); b.append(theServerBase); b.append('?'); b.append(Constants.PARAM_PAGINGACTION); b.append('='); b.append(URLEncoder.encode(theSearchId, "UTF-8")); b.append('&'); b.append(Constants.PARAM_PAGINGOFFSET); b.append('='); b.append(theOffset); b.append('&'); b.append(Constants.PARAM_COUNT); b.append('='); b.append(theCount); if (theResponseEncoding != null) { b.append('&'); b.append(Constants.PARAM_FORMAT); b.append('='); b.append(theResponseEncoding.getRequestContentType()); } if (thePrettyPrint) { b.append('&'); b.append(Constants.PARAM_PRETTY); b.append('='); b.append(Constants.PARAM_PRETTY_VALUE_TRUE); } if (theIncludes != null) { for (Include nextInclude : theIncludes) { if (isNotBlank(nextInclude.getValue())) { b.append('&'); b.append(Constants.PARAM_INCLUDE); b.append('='); b.append(URLEncoder.encode(nextInclude.getValue(), "UTF-8")); } } } if (theBundleType != null) { b.append('&'); b.append(Constants.PARAM_BUNDLETYPE); b.append('='); b.append(theBundleType.getCode()); } return b.toString(); } catch (UnsupportedEncodingException e) { throw new Error("UTF-8 not supported", e);// should not happen } } /** * @TODO: this method is only called from one place and should be removed anyway */ public static EncodingEnum determineRequestEncoding(RequestDetails theReq) { EncodingEnum retVal = determineRequestEncodingNoDefault(theReq); if (retVal != null) { return retVal; } return EncodingEnum.XML; } public static EncodingEnum determineRequestEncodingNoDefault(RequestDetails theReq) { ResponseEncoding retVal = determineRequestEncodingNoDefaultReturnRE(theReq); if (retVal == null) { return null; } return retVal.getEncoding(); } private static ResponseEncoding determineRequestEncodingNoDefaultReturnRE(RequestDetails theReq) { ResponseEncoding retVal = null; List<String> headers = theReq.getHeaders(Constants.HEADER_CONTENT_TYPE); if (headers != null) { Iterator<String> acceptValues = headers.iterator(); if (acceptValues != null) { while (acceptValues.hasNext() && retVal == null) { String nextAcceptHeaderValue = acceptValues.next(); if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) { for (String nextPart : nextAcceptHeaderValue.split(",")) { int scIdx = nextPart.indexOf(';'); if (scIdx == 0) { continue; } if (scIdx != -1) { nextPart = nextPart.substring(0, scIdx); } nextPart = nextPart.trim(); EncodingEnum encoding = EncodingEnum.forContentType(nextPart); if (encoding != null) { retVal = new ResponseEncoding(theReq.getServer().getFhirContext(), encoding, nextPart); break; } } } } } } return retVal; } /** * Returns null if the request doesn't express that it wants FHIR. If it expresses that it wants XML and JSON * equally, returns thePrefer. */ public static ResponseEncoding determineResponseEncodingNoDefault(RequestDetails theReq, EncodingEnum thePrefer) { String[] format = theReq.getParameters().get(Constants.PARAM_FORMAT); if (format != null) { for (String nextFormat : format) { EncodingEnum retVal = EncodingEnum.forContentType(nextFormat); if (retVal != null) { return new ResponseEncoding(theReq.getServer().getFhirContext(), retVal, nextFormat); } } } /* * Some browsers (e.g. FF) request "application/xml" in their Accept header, * and we generally want to treat this as a preference for FHIR XML even if * it's not the FHIR version of the CT, which should be "application/xml+fhir". * * When we're serving up Binary resources though, we are a bit more strict, * since Binary is supposed to use native content types unless the client has * explicitly requested FHIR. */ boolean strict = false; if ("Binary".equals(theReq.getResourceName())) { strict = true; } /* * The Accept header is kind of ridiculous, e.g. */ // text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, */*;q=0.5 List<String> acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT); float bestQ = -1f; ResponseEncoding retVal = null; if (acceptValues != null) { for (String nextAcceptHeaderValue : acceptValues) { StringTokenizer tok = new StringTokenizer(nextAcceptHeaderValue, ","); while (tok.hasMoreTokens()) { String nextToken = tok.nextToken(); int startSpaceIndex = -1; for (int i = 0; i < nextToken.length(); i++) { if (nextToken.charAt(i) != ' ') { startSpaceIndex = i; break; } } if (startSpaceIndex == -1) { continue; } int endSpaceIndex = -1; for (int i = startSpaceIndex; i < nextToken.length(); i++) { if (nextToken.charAt(i) == ' ' || nextToken.charAt(i) == ';') { endSpaceIndex = i; break; } } float q = 1.0f; ResponseEncoding encoding; if (endSpaceIndex == -1) { if (startSpaceIndex == 0) { encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken); } else { encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken.substring(startSpaceIndex)); } } else { encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken.substring(startSpaceIndex, endSpaceIndex)); String remaining = nextToken.substring(endSpaceIndex + 1); StringTokenizer qualifierTok = new StringTokenizer(remaining, ";"); while (qualifierTok.hasMoreTokens()) { String nextQualifier = qualifierTok.nextToken(); int equalsIndex = nextQualifier.indexOf('='); if (equalsIndex != -1) { String nextQualifierKey = nextQualifier.substring(0, equalsIndex).trim(); String nextQualifierValue = nextQualifier.substring(equalsIndex + 1, nextQualifier.length()).trim(); if (nextQualifierKey.equals("q")) { try { q = Float.parseFloat(nextQualifierValue); q = Math.max(q, 0.0f); } catch (NumberFormatException e) { ourLog.debug("Invalid Accept header q value: {}", nextQualifierValue); } } } } } if (encoding != null) { if (q > bestQ || (q == bestQ && encoding.getEncoding() == thePrefer)) { retVal = encoding; bestQ = q; } } } } } /* * If the client hasn't given any indication about which response * encoding they want, let's try the request encoding in case that * is useful (basically this catches the case where the request * has a Content-Type header but not an Accept header) */ if (retVal == null) { retVal = determineRequestEncodingNoDefaultReturnRE(theReq); } return retVal; } /** * Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's * <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header. */ public static ResponseEncoding determineResponseEncodingWithDefault(RequestDetails theReq) { ResponseEncoding retVal = determineResponseEncodingNoDefault(theReq, theReq.getServer().getDefaultResponseEncoding()); if (retVal == null) { retVal = new ResponseEncoding(theReq.getServer().getFhirContext(), theReq.getServer().getDefaultResponseEncoding(), null); } return retVal; } public static Set<SummaryEnum> determineSummaryMode(RequestDetails theRequest) { Map<String, String[]> requestParams = theRequest.getParameters(); Set<SummaryEnum> retVal = SummaryEnumParameter.getSummaryValueOrNull(theRequest); if (retVal == null) { /* * HAPI originally supported a custom parameter called _narrative, but this has been superceded by an official * parameter called _summary */ String[] narrative = requestParams.get(Constants.PARAM_NARRATIVE); if (narrative != null && narrative.length > 0) { try { NarrativeModeEnum narrativeMode = NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]); switch (narrativeMode) { case NORMAL: retVal = Collections.singleton(SummaryEnum.FALSE); break; case ONLY: retVal = Collections.singleton(SummaryEnum.TEXT); break; case SUPPRESS: retVal = Collections.singleton(SummaryEnum.DATA); break; } } catch (IllegalArgumentException e) { ourLog.debug("Invalid {} parameter: {}", Constants.PARAM_NARRATIVE, narrative[0]); } } } if (retVal == null) { retVal = Collections.singleton(SummaryEnum.FALSE); } return retVal; } public static Integer extractCountParameter(RequestDetails theRequest) { return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT); } public static IPrimitiveType<Date> extractLastUpdatedFromResource(IBaseResource theResource) { IPrimitiveType<Date> lastUpdated = null; if (theResource instanceof IResource) { lastUpdated = ResourceMetadataKeyEnum.UPDATED.get((IResource) theResource); } else if (theResource instanceof IAnyResource) { lastUpdated = new InstantDt(((IAnyResource) theResource).getMeta().getLastUpdated()); } return lastUpdated; } public static IIdType fullyQualifyResourceIdOrReturnNull(IRestfulServerDefaults theServer, IBaseResource theResource, String theServerBase, IIdType theResourceId) { IIdType retVal = null; if (theResourceId.hasIdPart() && isNotBlank(theServerBase)) { String resName = theResourceId.getResourceType(); if (theResource != null && isBlank(resName)) { resName = theServer.getFhirContext().getResourceDefinition(theResource).getName(); } if (isNotBlank(resName)) { retVal = theResourceId.withServerBase(theServerBase, resName); } } return retVal; } private static ResponseEncoding getEncodingForContentType(FhirContext theFhirContext, boolean theStrict, String theContentType) { EncodingEnum encoding; if (theStrict) { encoding = EncodingEnum.forContentTypeStrict(theContentType); } else { encoding = EncodingEnum.forContentType(theContentType); } if (encoding == null) { return null; } return new ResponseEncoding(theFhirContext, encoding, theContentType); } public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) { // Determine response encoding EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails).getEncoding(); IParser parser; switch (responseEncoding) { case JSON: parser = theContext.newJsonParser(); break; case XML: default: parser = theContext.newXmlParser(); break; } configureResponseParser(theRequestDetails, parser); return parser; } public static Set<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) { Set<String> retVal = new HashSet<String>(); Enumeration<String> acceptValues = theRequest.getHeaders(Constants.HEADER_ACCEPT); if (acceptValues != null) { float bestQ = -1f; while (acceptValues.hasMoreElements()) { String nextAcceptHeaderValue = acceptValues.nextElement(); Matcher m = ACCEPT_HEADER_PATTERN.matcher(nextAcceptHeaderValue); float q = 1.0f; while (m.find()) { String contentTypeGroup = m.group(1); if (isNotBlank(contentTypeGroup)) { String name = m.group(3); String value = m.group(4); if (name != null && value != null) { if ("q".equals(name)) { try { q = Float.parseFloat(value); q = Math.max(q, 0.0f); } catch (NumberFormatException e) { ourLog.debug("Invalid Accept header q value: {}", value); } } } if (q > bestQ) { retVal.clear(); bestQ = q; } if (q == bestQ) { retVal.add(contentTypeGroup.trim()); } } if (!",".equals(m.group(5))) { break; } } } } return retVal; } public static PreferReturnEnum parsePreferHeader(String theValue) { if (isBlank(theValue)) { return null; } StringTokenizer tok = new StringTokenizer(theValue, ","); while (tok.hasMoreTokens()) { String next = tok.nextToken(); int eqIndex = next.indexOf('='); if (eqIndex == -1 || eqIndex >= next.length() - 2) { continue; } String key = next.substring(0, eqIndex).trim(); if (key.equals(Constants.HEADER_PREFER_RETURN) == false) { continue; } String value = next.substring(eqIndex + 1).trim(); if (value.length() < 2) { continue; } if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) { value = value.substring(1, value.length() - 1); } return PreferReturnEnum.fromHeaderValue(value); } return null; } public static boolean prettyPrintResponse(IRestfulServerDefaults theServer, RequestDetails theRequest) { Map<String, String[]> requestParams = theRequest.getParameters(); String[] pretty = requestParams.get(Constants.PARAM_PRETTY); boolean prettyPrint; if (pretty != null && pretty.length > 0) { if (Constants.PARAM_PRETTY_VALUE_TRUE.equals(pretty[0])) { prettyPrint = true; } else { prettyPrint = false; } } else { prettyPrint = theServer.isDefaultPrettyPrint(); List<String> acceptValues = theRequest.getHeaders(Constants.HEADER_ACCEPT); if (acceptValues != null) { for (String nextAcceptHeaderValue : acceptValues) { if (nextAcceptHeaderValue.contains("pretty=true")) { prettyPrint = true; } } } } return prettyPrint; } public static Object streamResponseAsBundle(IRestfulServerDefaults theServer, Bundle bundle, Set<SummaryEnum> theSummaryMode, boolean respondGzip, RequestDetails theRequestDetails) throws IOException { int status = 200; // Determine response encoding EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails).getEncoding(); String contentType = responseEncoding.getBundleContentType(); String charset = Constants.CHARSET_NAME_UTF8; Writer writer = theRequestDetails.getResponse().getResponseWriter(status, null, contentType, charset, respondGzip); try { IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), theRequestDetails); if (theSummaryMode.contains(SummaryEnum.TEXT)) { parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); } parser.encodeBundleToWriter(bundle, writer); } catch (Exception e) { // always send a response, even if the parsing went wrong } //FIXME resource leak return theRequestDetails.getResponse().sendWriterResponse(status, contentType, charset, writer); } public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int stausCode, boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails) throws IOException { return streamResponseAsResource(theServer, theResource, theSummaryMode, stausCode, null, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null); } public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStausCode, String theStatusMessage, boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType<Date> theOperationResourceLastUpdated) throws IOException { IRestfulResponse restUtil = theRequestDetails.getResponse(); // Determine response encoding ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, theServer.getDefaultResponseEncoding()); String serverBase = theRequestDetails.getFhirServerBase(); IIdType fullId = null; if (theOperationResourceId != null) { fullId = theOperationResourceId; } else if (theResource != null) { if (theResource.getIdElement() != null) { IIdType resourceId = theResource.getIdElement(); fullId = fullyQualifyResourceIdOrReturnNull(theServer, theResource, serverBase, resourceId); } } if (theAddContentLocationHeader && fullId != null) { if (theServer.getFhirContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { restUtil.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue()); } restUtil.addHeader(Constants.HEADER_LOCATION, fullId.getValue()); } if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) { if (fullId != null && fullId.hasVersionIdPart()) { restUtil.addHeader(Constants.HEADER_ETAG, "W/\"" + fullId.getVersionIdPart() + '"'); } } String contentType; if (theResource instanceof IBaseBinary && responseEncoding == null) { IBaseBinary bin = (IBaseBinary) theResource; if (isNotBlank(bin.getContentType())) { contentType = bin.getContentType(); } else { contentType = Constants.CT_OCTET_STREAM; } // Force binary resources to download - This is a security measure to prevent // malicious images or HTML blocks being served up as content. restUtil.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;"); return restUtil.sendAttachmentResponse(bin, theStausCode, contentType); } // Ok, we're not serving a binary resource, so apply default encoding if (responseEncoding == null) { responseEncoding = new ResponseEncoding(theServer.getFhirContext(), theServer.getDefaultResponseEncoding(), null); } boolean encodingDomainResourceAsText = theSummaryMode.contains(SummaryEnum.TEXT); if (encodingDomainResourceAsText) { /* * If the user requests "text" for a bundle, only suppress the non text elements in the Element.entry.resource * parts, we're not streaming just the narrative as HTML (since bundles don't even * have one) */ if ("Bundle".equals(theServer.getFhirContext().getResourceDefinition(theResource).getName())) { encodingDomainResourceAsText = false; } } /* * Last-Modified header */ IPrimitiveType<Date> lastUpdated; if (theOperationResourceLastUpdated != null) { lastUpdated = theOperationResourceLastUpdated; } else { lastUpdated = extractLastUpdatedFromResource(theResource); } if (lastUpdated != null && lastUpdated.isEmpty() == false) { restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue())); } /* * Category header (DSTU1 only) */ if (theResource instanceof IResource && theServer.getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) { TagList list = (TagList) ((IResource) theResource).getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); if (list != null) { for (Tag tag : list) { if (StringUtils.isNotBlank(tag.getTerm())) { restUtil.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); } } } } /* * Stream the response body */ if (theResource == null) { contentType = null; } else if (encodingDomainResourceAsText) { contentType = Constants.CT_HTML; } else { contentType = responseEncoding.getResourceContentType(); } String charset = Constants.CHARSET_NAME_UTF8; Writer writer = restUtil.getResponseWriter(theStausCode, theStatusMessage, contentType, charset, respondGzip); if (theResource == null) { // No response is being returned } else if (encodingDomainResourceAsText && theResource instanceof IResource) { writer.append(((IResource) theResource).getText().getDiv().getValueAsString()); } else { IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails); parser.encodeResourceToWriter(theResource, writer); } //FIXME resource leak return restUtil.sendWriterResponse(theStausCode, contentType, charset, writer); } public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) { String[] retVal = theRequest.getParameters().get(theParamName); if (retVal == null) { return null; } try { return Integer.parseInt(retVal[0]); } catch (NumberFormatException e) { ourLog.debug("Failed to parse {} value '{}': {}", new Object[] { theParamName, retVal[0], e }); return null; } } // static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) { // String countString = theRequest.getParameter(name); // Integer count = null; // if (isNotBlank(countString)) { // try { // count = Integer.parseInt(countString); // } catch (NumberFormatException e) { // ourLog.debug("Failed to parse _count value '{}': {}", countString, e); // } // } // return count; // } public static void validateResourceListNotNull(List<? extends IBaseResource> theResourceList) { if (theResourceList == null) { throw new InternalErrorException("IBundleProvider returned a null list of resources - This is not allowed"); } } private static enum NarrativeModeEnum { NORMAL, ONLY, SUPPRESS; public static NarrativeModeEnum valueOfCaseInsensitive(String theCode) { return valueOf(NarrativeModeEnum.class, theCode.toUpperCase()); } } /** * Return type for {@link RestfulServerUtils#determineRequestEncodingNoDefault(RequestDetails)} */ public static class ResponseEncoding { private final EncodingEnum myEncoding; private final Boolean myNonLegacy; public ResponseEncoding(FhirContext theCtx, EncodingEnum theEncoding, String theContentType) { super(); myEncoding = theEncoding; if (theContentType != null) { if (theContentType.equals(EncodingEnum.JSON_PLAIN_STRING) || theContentType.equals(EncodingEnum.XML_PLAIN_STRING)) { FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion(); myNonLegacy = ctxtEnum.isNewerThan(FhirVersionEnum.DSTU3) || (ctxtEnum.isEquivalentTo(FhirVersionEnum.DSTU3) && !"1.4.0".equals(theCtx.getVersion().getVersion().getFhirVersionString())); } else { myNonLegacy = EncodingEnum.isNonLegacy(theContentType); } } else { FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion(); if (ctxtEnum.isOlderThan(FhirVersionEnum.DSTU3) || (ctxtEnum.isEquivalentTo(FhirVersionEnum.DSTU3) && "1.4.0".equals(theCtx.getVersion().getVersion().getFhirVersionString()))) { myNonLegacy = null; } else { myNonLegacy = Boolean.TRUE; } } } public EncodingEnum getEncoding() { return myEncoding; } public String getResourceContentType() { if (Boolean.TRUE.equals(isNonLegacy())) { return getEncoding().getResourceContentTypeNonLegacy(); } return getEncoding().getResourceContentType(); } public Boolean isNonLegacy() { return myNonLegacy; } } public static void addAcceptHeaderToRequest(EncodingEnum theEncoding, IHttpRequest theHttpRequest, FhirContext theContext) { if (theEncoding == null) { if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1) == false) { theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_LEGACY); } else { theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_NON_LEGACY); } } else if (theEncoding == EncodingEnum.JSON) { if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1) == false) { theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON); } else { theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY); } } else if (theEncoding == EncodingEnum.XML) { if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1) == false) { theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML); } else { theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY); } } } }