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}