001package ca.uhn.fhir.rest.server; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2016 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022import static org.apache.commons.lang3.StringUtils.isBlank; 023import static org.apache.commons.lang3.StringUtils.isNotBlank; 024 025import java.io.IOException; 026import java.io.UnsupportedEncodingException; 027import java.io.Writer; 028import java.net.URLEncoder; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Collections; 032import java.util.Date; 033import java.util.Enumeration; 034import java.util.HashSet; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Map; 038import java.util.Set; 039import java.util.StringTokenizer; 040import java.util.regex.Matcher; 041import java.util.regex.Pattern; 042 043import javax.servlet.http.HttpServletRequest; 044 045import org.apache.commons.lang3.StringUtils; 046import org.apache.http.client.utils.DateUtils; 047import org.hl7.fhir.instance.model.api.IAnyResource; 048import org.hl7.fhir.instance.model.api.IBaseBinary; 049import org.hl7.fhir.instance.model.api.IBaseResource; 050import org.hl7.fhir.instance.model.api.IIdType; 051 052import ca.uhn.fhir.context.FhirContext; 053import ca.uhn.fhir.context.FhirVersionEnum; 054import ca.uhn.fhir.context.RuntimeResourceDefinition; 055import ca.uhn.fhir.model.api.Bundle; 056import ca.uhn.fhir.model.api.IResource; 057import ca.uhn.fhir.model.api.Include; 058import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 059import ca.uhn.fhir.model.api.Tag; 060import ca.uhn.fhir.model.api.TagList; 061import ca.uhn.fhir.model.primitive.IdDt; 062import ca.uhn.fhir.model.primitive.InstantDt; 063import ca.uhn.fhir.model.valueset.BundleTypeEnum; 064import ca.uhn.fhir.parser.IParser; 065import ca.uhn.fhir.rest.api.PreferReturnEnum; 066import ca.uhn.fhir.rest.api.SummaryEnum; 067import ca.uhn.fhir.rest.method.ElementsParameter; 068import ca.uhn.fhir.rest.method.RequestDetails; 069import ca.uhn.fhir.rest.method.SummaryEnumParameter; 070import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 071import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 072 073public class RestfulServerUtils { 074 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*)?(,?)"); 075 076 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class); 077 078 private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<String>(Arrays.asList("Bundle", "*.text")); 079 080 public static void addProfileToBundleEntry(FhirContext theContext, IBaseResource theResource, String theServerBase) { 081 if (theResource instanceof IResource) { 082 if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { 083 List<IdDt> tl = ResourceMetadataKeyEnum.PROFILES.get((IResource) theResource); 084 if (tl == null) { 085 tl = new ArrayList<IdDt>(); 086 ResourceMetadataKeyEnum.PROFILES.put((IResource) theResource, tl); 087 } 088 089 RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource); 090 String profile = nextDef.getResourceProfile(theServerBase); 091 if (isNotBlank(profile)) { 092 tl.add(new IdDt(profile)); 093 } 094 } else { 095 TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get((IResource) theResource); 096 if (tl == null) { 097 tl = new TagList(); 098 ResourceMetadataKeyEnum.TAG_LIST.put((IResource) theResource, tl); 099 } 100 101 RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource); 102 String profile = nextDef.getResourceProfile(theServerBase); 103 if (isNotBlank(profile)) { 104 tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null)); 105 } 106 } 107 } 108 } 109 110 public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) { 111 // Pretty print 112 boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails); 113 114 parser.setPrettyPrint(prettyPrint); 115 parser.setServerBaseUrl(theRequestDetails.getFhirServerBase()); 116 117 // Summary mode 118 Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails); 119 120 // _elements 121 Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails); 122 if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) { 123 throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters"); 124 } 125 Set<String> elementsAppliesTo = null; 126 if (elements != null && isNotBlank(theRequestDetails.getResourceName())) { 127 elementsAppliesTo = Collections.singleton(theRequestDetails.getResourceName()); 128 } 129 130 if (summaryMode != null) { 131 if (summaryMode.contains(SummaryEnum.COUNT)) { 132 parser.setEncodeElements(Collections.singleton("Bundle.total")); 133 } else if (summaryMode.contains(SummaryEnum.TEXT)) { 134 parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); 135 } else { 136 parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA)); 137 parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE)); 138 } 139 } 140 if (elements != null && elements.size() > 0) { 141 Set<String> newElements = new HashSet<String>(); 142 for (String next : elements) { 143 newElements.add("*." + next); 144 } 145 parser.setEncodeElements(newElements); 146 parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo); 147 } 148 } 149 150 public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint, 151 BundleTypeEnum theBundleType) { 152 try { 153 StringBuilder b = new StringBuilder(); 154 b.append(theServerBase); 155 b.append('?'); 156 b.append(Constants.PARAM_PAGINGACTION); 157 b.append('='); 158 b.append(URLEncoder.encode(theSearchId, "UTF-8")); 159 160 b.append('&'); 161 b.append(Constants.PARAM_PAGINGOFFSET); 162 b.append('='); 163 b.append(theOffset); 164 b.append('&'); 165 b.append(Constants.PARAM_COUNT); 166 b.append('='); 167 b.append(theCount); 168 if (theResponseEncoding != null) { 169 b.append('&'); 170 b.append(Constants.PARAM_FORMAT); 171 b.append('='); 172 b.append(theResponseEncoding.getRequestContentType()); 173 } 174 if (thePrettyPrint) { 175 b.append('&'); 176 b.append(Constants.PARAM_PRETTY); 177 b.append('='); 178 b.append(Constants.PARAM_PRETTY_VALUE_TRUE); 179 } 180 181 if (theIncludes != null) { 182 for (Include nextInclude : theIncludes) { 183 if (isNotBlank(nextInclude.getValue())) { 184 b.append('&'); 185 b.append(Constants.PARAM_INCLUDE); 186 b.append('='); 187 b.append(URLEncoder.encode(nextInclude.getValue(), "UTF-8")); 188 } 189 } 190 } 191 192 if (theBundleType != null) { 193 b.append('&'); 194 b.append(Constants.PARAM_BUNDLETYPE); 195 b.append('='); 196 b.append(theBundleType.getCode()); 197 } 198 199 return b.toString(); 200 } catch (UnsupportedEncodingException e) { 201 throw new Error("UTF-8 not supported", e);// should not happen 202 } 203 } 204 205 public static EncodingEnum determineRequestEncoding(RequestDetails theReq) { 206 EncodingEnum retVal = determineRequestEncodingNoDefault(theReq); 207 if (retVal != null) { 208 return retVal; 209 } 210 return EncodingEnum.XML; 211 } 212 213 public static EncodingEnum determineRequestEncodingNoDefault(RequestDetails theReq) { 214 EncodingEnum retVal = null; 215 Iterator<String> acceptValues = theReq.getHeaders(Constants.HEADER_CONTENT_TYPE).iterator(); 216 if (acceptValues != null) { 217 while (acceptValues.hasNext() && retVal == null) { 218 String nextAcceptHeaderValue = acceptValues.next(); 219 if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) { 220 for (String nextPart : nextAcceptHeaderValue.split(",")) { 221 int scIdx = nextPart.indexOf(';'); 222 if (scIdx == 0) { 223 continue; 224 } 225 if (scIdx != -1) { 226 nextPart = nextPart.substring(0, scIdx); 227 } 228 nextPart = nextPart.trim(); 229 retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart); 230 if (retVal != null) { 231 break; 232 } 233 } 234 } 235 } 236 } 237 return retVal; 238 } 239 240 /** 241 * Returns null if the request doesn't express that it wants FHIR. If it expresses that it wants XML and JSON equally, returns thePrefer. 242 */ 243 public static EncodingEnum determineResponseEncodingNoDefault(RequestDetails theReq, EncodingEnum thePrefer) { 244 String[] format = theReq.getParameters().get(Constants.PARAM_FORMAT); 245 if (format != null) { 246 for (String nextFormat : format) { 247 EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat); 248 if (retVal != null) { 249 return retVal; 250 } 251 } 252 } 253 254 /* 255 * The Accept header is kind of ridiculous, e.g. 256 */ 257 // text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, */*;q=0.5 258 259 List<String> acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT); 260 float bestQ = -1f; 261 EncodingEnum retVal = null; 262 if (acceptValues != null) { 263 for (String nextAcceptHeaderValue : acceptValues) { 264 StringTokenizer tok = new StringTokenizer(nextAcceptHeaderValue, ","); 265 while (tok.hasMoreTokens()) { 266 String nextToken = tok.nextToken(); 267 int startSpaceIndex = -1; 268 for (int i = 0; i < nextToken.length(); i++) { 269 if (nextToken.charAt(i) != ' ') { 270 startSpaceIndex = i; 271 break; 272 } 273 } 274 275 if (startSpaceIndex == -1) { 276 continue; 277 } 278 279 int endSpaceIndex = -1; 280 for (int i = startSpaceIndex; i < nextToken.length(); i++) { 281 if (nextToken.charAt(i) == ' ' || nextToken.charAt(i) == ';') { 282 endSpaceIndex = i; 283 break; 284 } 285 } 286 287 float q = 1.0f; 288 EncodingEnum encoding; 289 boolean pretty = false; 290 if (endSpaceIndex == -1) { 291 if (startSpaceIndex == 0) { 292 encoding = Constants.FORMAT_VAL_TO_ENCODING.get(nextToken); 293 } else { 294 encoding = Constants.FORMAT_VAL_TO_ENCODING.get(nextToken.substring(startSpaceIndex)); 295 } 296 } else { 297 encoding = Constants.FORMAT_VAL_TO_ENCODING.get(nextToken.substring(startSpaceIndex, endSpaceIndex)); 298 String remaining = nextToken.substring(endSpaceIndex + 1); 299 StringTokenizer qualifierTok = new StringTokenizer(remaining, ";"); 300 while (qualifierTok.hasMoreTokens()) { 301 String nextQualifier = qualifierTok.nextToken(); 302 int equalsIndex = nextQualifier.indexOf('='); 303 if (equalsIndex != -1) { 304 String nextQualifierKey = nextQualifier.substring(0, equalsIndex).trim(); 305 String nextQualifierValue = nextQualifier.substring(equalsIndex + 1, nextQualifier.length()).trim(); 306 if (nextQualifierKey.equals("q")) { 307 try { 308 q = Float.parseFloat(nextQualifierValue); 309 q = Math.max(q, 0.0f); 310 } catch (NumberFormatException e) { 311 ourLog.debug("Invalid Accept header q value: {}", nextQualifierValue); 312 } 313 } 314 } 315 } 316 } 317 318 if (encoding != null) { 319 if (q > bestQ || (q == bestQ && encoding == thePrefer)) { 320 retVal = encoding; 321 bestQ = q; 322 } 323 } 324 325 } 326 327 // 328 // 329 // 330 // 331 // Matcher m = ACCEPT_HEADER_PATTERN.matcher(nextAcceptHeaderValue); 332 // float q = 1.0f; 333 // while (m.find()) { 334 // String contentTypeGroup = m.group(1); 335 // EncodingEnum encoding = Constants.FORMAT_VAL_TO_ENCODING.get(contentTypeGroup); 336 // if (encoding != null) { 337 // 338 // String name = m.group(3); 339 // String value = m.group(4); 340 // if (name != null && value != null) { 341 // if ("q".equals(name)) { 342 // try { 343 // q = Float.parseFloat(value); 344 // q = Math.max(q, 0.0f); 345 // } catch (NumberFormatException e) { 346 // ourLog.debug("Invalid Accept header q value: {}", value); 347 // } 348 // } 349 // } 350 // } 351 // 352 // if (encoding != null) { 353 // if (q > bestQ || (q == bestQ && encoding == thePrefer)) { 354 // retVal = encoding; 355 // bestQ = q; 356 // } 357 // } 358 // 359 // if (!",".equals(m.group(5))) { 360 // break; 361 // } 362 // } 363 // 364 } 365 366 return retVal; 367 } 368 return null; 369 } 370 371 /** 372 * 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. 373 */ 374 public static EncodingEnum determineResponseEncodingWithDefault(RequestDetails theReq) { 375 EncodingEnum retVal = determineResponseEncodingNoDefault(theReq, theReq.getServer().getDefaultResponseEncoding()); 376 if (retVal == null) { 377 retVal = theReq.getServer().getDefaultResponseEncoding(); 378 } 379 return retVal; 380 } 381 382 public static Set<SummaryEnum> determineSummaryMode(RequestDetails theRequest) { 383 Map<String, String[]> requestParams = theRequest.getParameters(); 384 385 Set<SummaryEnum> retVal = SummaryEnumParameter.getSummaryValueOrNull(theRequest); 386 387 if (retVal == null) { 388 /* 389 * HAPI originally supported a custom parameter called _narrative, but this has been superceded by an official parameter called _summary 390 */ 391 String[] narrative = requestParams.get(Constants.PARAM_NARRATIVE); 392 if (narrative != null && narrative.length > 0) { 393 try { 394 NarrativeModeEnum narrativeMode = NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]); 395 switch (narrativeMode) { 396 case NORMAL: 397 retVal = Collections.singleton(SummaryEnum.FALSE); 398 break; 399 case ONLY: 400 retVal = Collections.singleton(SummaryEnum.TEXT); 401 break; 402 case SUPPRESS: 403 retVal = Collections.singleton(SummaryEnum.DATA); 404 break; 405 } 406 } catch (IllegalArgumentException e) { 407 ourLog.debug("Invalid {} parameger: {}", Constants.PARAM_NARRATIVE, narrative[0]); 408 } 409 } 410 } 411 if (retVal == null) { 412 retVal = Collections.singleton(SummaryEnum.FALSE); 413 } 414 415 return retVal; 416 } 417 418 public static Integer extractCountParameter(RequestDetails theRequest) { 419 return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT); 420 } 421 422 public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) { 423 424 // Determine response encoding 425 EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails); 426 IParser parser; 427 switch (responseEncoding) { 428 case JSON: 429 parser = theContext.newJsonParser(); 430 break; 431 case XML: 432 default: 433 parser = theContext.newXmlParser(); 434 break; 435 } 436 437 configureResponseParser(theRequestDetails, parser); 438 439 return parser; 440 } 441 442 public static Set<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) { 443 Set<String> retVal = new HashSet<String>(); 444 445 Enumeration<String> acceptValues = theRequest.getHeaders(Constants.HEADER_ACCEPT); 446 if (acceptValues != null) { 447 float bestQ = -1f; 448 while (acceptValues.hasMoreElements()) { 449 String nextAcceptHeaderValue = acceptValues.nextElement(); 450 Matcher m = ACCEPT_HEADER_PATTERN.matcher(nextAcceptHeaderValue); 451 float q = 1.0f; 452 while (m.find()) { 453 String contentTypeGroup = m.group(1); 454 if (isNotBlank(contentTypeGroup)) { 455 456 String name = m.group(3); 457 String value = m.group(4); 458 if (name != null && value != null) { 459 if ("q".equals(name)) { 460 try { 461 q = Float.parseFloat(value); 462 q = Math.max(q, 0.0f); 463 } 464 catch (NumberFormatException e) { 465 ourLog.debug("Invalid Accept header q value: {}", value); 466 } 467 } 468 } 469 470 if (q > bestQ) { 471 retVal.clear(); 472 bestQ = q; 473 } 474 475 if (q == bestQ) { 476 retVal.add(contentTypeGroup.trim()); 477 } 478 479 } 480 481 if (!",".equals(m.group(5))) { 482 break; 483 } 484 } 485 486 } 487 } 488 489 return retVal; 490 } 491 492 public static PreferReturnEnum parsePreferHeader(String theValue) { 493 if (isBlank(theValue)) { 494 return null; 495 } 496 497 StringTokenizer tok = new StringTokenizer(theValue, ","); 498 while (tok.hasMoreTokens()) { 499 String next = tok.nextToken(); 500 int eqIndex = next.indexOf('='); 501 if (eqIndex == -1 || eqIndex >= next.length() - 2) { 502 continue; 503 } 504 505 String key = next.substring(0, eqIndex).trim(); 506 if (key.equals(Constants.HEADER_PREFER_RETURN) == false) { 507 continue; 508 } 509 510 String value = next.substring(eqIndex + 1).trim(); 511 if (value.length() < 2) { 512 continue; 513 } 514 if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) { 515 value = value.substring(1, value.length() - 1); 516 } 517 518 return PreferReturnEnum.fromHeaderValue(value); 519 } 520 521 return null; 522 } 523 524 public static boolean prettyPrintResponse(IRestfulServerDefaults theServer, RequestDetails theRequest) { 525 Map<String, String[]> requestParams = theRequest.getParameters(); 526 String[] pretty = requestParams.get(Constants.PARAM_PRETTY); 527 boolean prettyPrint; 528 if (pretty != null && pretty.length > 0) { 529 if (Constants.PARAM_PRETTY_VALUE_TRUE.equals(pretty[0])) { 530 prettyPrint = true; 531 } else { 532 prettyPrint = false; 533 } 534 } else { 535 prettyPrint = theServer.isDefaultPrettyPrint(); 536 List<String> acceptValues = theRequest.getHeaders(Constants.HEADER_ACCEPT); 537 if (acceptValues != null) { 538 for (String nextAcceptHeaderValue : acceptValues) { 539 if (nextAcceptHeaderValue.contains("pretty=true")) { 540 prettyPrint = true; 541 } 542 } 543 } 544 } 545 return prettyPrint; 546 } 547 548 public static Object streamResponseAsBundle(IRestfulServerDefaults theServer, Bundle bundle, Set<SummaryEnum> theSummaryMode, boolean respondGzip, RequestDetails theRequestDetails) 549 throws IOException { 550 551 int status = 200; 552 553 // Determine response encoding 554 EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails); 555 556 String contentType = responseEncoding.getBundleContentType(); 557 558 String charset = Constants.CHARSET_NAME_UTF8; 559 Writer writer = theRequestDetails.getResponse().getResponseWriter(status, contentType, charset, respondGzip); 560 561 try { 562 IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), theRequestDetails); 563 if (theSummaryMode.contains(SummaryEnum.TEXT)) { 564 parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); 565 } 566 parser.encodeBundleToWriter(bundle, writer); 567 } 568 catch (Exception e) { 569 //always send a response, even if the parsing went wrong 570 } 571 return theRequestDetails.getResponse().sendWriterResponse(status, contentType, charset, writer); 572 } 573 574 public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, 575 int stausCode, boolean theAddContentLocationHeader, boolean respondGzip, 576 RequestDetails theRequestDetails) 577 throws IOException { 578 IRestfulResponse restUtil = theRequestDetails.getResponse(); 579 580 // Determine response encoding 581 EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, 582 theServer.getDefaultResponseEncoding()); 583 584 String serverBase = theRequestDetails.getFhirServerBase(); 585 if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart() 586 && isNotBlank(serverBase)) { 587 String resName = theServer.getFhirContext().getResourceDefinition(theResource).getName(); 588 IIdType fullId = theResource.getIdElement().withServerBase(serverBase, resName); 589 restUtil.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue()); 590 } 591 592 if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) { 593 if (theResource.getIdElement().hasVersionIdPart()) { 594 restUtil.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getIdElement().getVersionIdPart() + '"'); 595 } 596 } 597 598 if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) { 599 RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(theResource); 600 if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) { 601 addProfileToBundleEntry(theServer.getFhirContext(), theResource, serverBase); 602 } 603 } 604 605 String contentType; 606 if (theResource instanceof IBaseBinary && responseEncoding == null) { 607 IBaseBinary bin = (IBaseBinary) theResource; 608 if (isNotBlank(bin.getContentType())) { 609 contentType = bin.getContentType(); 610 } else { 611 contentType = Constants.CT_OCTET_STREAM; 612 } 613 // Force binary resources to download - This is a security measure to prevent 614 // malicious images or HTML blocks being served up as content. 615 restUtil.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;"); 616 617 return restUtil.sendAttachmentResponse(bin, stausCode, contentType); 618 } 619 620 // Ok, we're not serving a binary resource, so apply default encoding 621 responseEncoding = responseEncoding != null ? responseEncoding : theServer.getDefaultResponseEncoding(); 622 623 boolean encodingDomainResourceAsText = theSummaryMode.contains(SummaryEnum.TEXT); 624 if (encodingDomainResourceAsText) { 625 /* 626 * 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 627 * have one) 628 */ 629 if ("Bundle".equals(theServer.getFhirContext().getResourceDefinition(theResource).getName())) { 630 encodingDomainResourceAsText = false; 631 } 632 } 633 634 if (encodingDomainResourceAsText) { 635 contentType = Constants.CT_HTML; 636 } else { 637 contentType = responseEncoding.getResourceContentType(); 638 } 639 String charset = Constants.CHARSET_NAME_UTF8; 640 641 if (theResource instanceof IResource) { 642 InstantDt lastUpdated = ResourceMetadataKeyEnum.UPDATED.get((IResource) theResource); 643 if (lastUpdated != null && lastUpdated.isEmpty() == false) { 644 restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue())); 645 } 646 647 TagList list = (TagList) ((IResource) theResource).getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); 648 if (list != null) { 649 for (Tag tag : list) { 650 if (StringUtils.isNotBlank(tag.getTerm())) { 651 restUtil.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); 652 } 653 } 654 } 655 } else { 656 Date lastUpdated = ((IAnyResource) theResource).getMeta().getLastUpdated(); 657 if (lastUpdated != null) { 658 restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated)); 659 } 660 } 661 662 Writer writer = restUtil.getResponseWriter(stausCode, contentType, charset, respondGzip); 663 664 if (encodingDomainResourceAsText && theResource instanceof IResource) { 665 writer.append(((IResource) theResource).getText().getDiv().getValueAsString()); 666 } else { 667 IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails); 668 parser.encodeResourceToWriter(theResource, writer); 669 } 670 671 return restUtil.sendWriterResponse(stausCode, contentType, charset, writer); 672 } 673 674 // static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) { 675 // String countString = theRequest.getParameter(name); 676 // Integer count = null; 677 // if (isNotBlank(countString)) { 678 // try { 679 // count = Integer.parseInt(countString); 680 // } catch (NumberFormatException e) { 681 // ourLog.debug("Failed to parse _count value '{}': {}", countString, e); 682 // } 683 // } 684 // return count; 685 // } 686 687 public static void validateResourceListNotNull(List<? extends IBaseResource> theResourceList) { 688 if (theResourceList == null) { 689 throw new InternalErrorException("IBundleProvider returned a null list of resources - This is not allowed"); 690 } 691 } 692 693 private static enum NarrativeModeEnum { 694 NORMAL, ONLY, SUPPRESS; 695 696 public static NarrativeModeEnum valueOfCaseInsensitive(String theCode) { 697 return valueOf(NarrativeModeEnum.class, theCode.toUpperCase()); 698 } 699 } 700 701 public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) { 702 String[] retVal = theRequest.getParameters().get(theParamName); 703 if (retVal == null) { 704 return null; 705 } 706 try { 707 return Integer.parseInt(retVal[0]); 708 } catch (NumberFormatException e) { 709 ourLog.debug("Failed to parse {} value '{}': {}", new Object[] { theParamName, retVal[0], e }); 710 return null; 711 } 712 } 713 714}