001/*
002 * #%L
003 * HAPI FHIR - Client Framework
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.client.method;
021
022import ca.uhn.fhir.context.ConfigurationException;
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.context.FhirVersionEnum;
025import ca.uhn.fhir.context.RuntimeResourceDefinition;
026import ca.uhn.fhir.i18n.Msg;
027import ca.uhn.fhir.model.api.IResource;
028import ca.uhn.fhir.model.api.Include;
029import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
030import ca.uhn.fhir.model.api.TagList;
031import ca.uhn.fhir.model.primitive.IdDt;
032import ca.uhn.fhir.model.primitive.InstantDt;
033import ca.uhn.fhir.parser.IParser;
034import ca.uhn.fhir.rest.annotation.At;
035import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
036import ca.uhn.fhir.rest.annotation.Count;
037import ca.uhn.fhir.rest.annotation.Elements;
038import ca.uhn.fhir.rest.annotation.IdParam;
039import ca.uhn.fhir.rest.annotation.IncludeParam;
040import ca.uhn.fhir.rest.annotation.Offset;
041import ca.uhn.fhir.rest.annotation.Operation;
042import ca.uhn.fhir.rest.annotation.OperationParam;
043import ca.uhn.fhir.rest.annotation.OptionalParam;
044import ca.uhn.fhir.rest.annotation.RawParam;
045import ca.uhn.fhir.rest.annotation.RequiredParam;
046import ca.uhn.fhir.rest.annotation.ResourceParam;
047import ca.uhn.fhir.rest.annotation.Since;
048import ca.uhn.fhir.rest.annotation.Sort;
049import ca.uhn.fhir.rest.annotation.TransactionParam;
050import ca.uhn.fhir.rest.annotation.Validate;
051import ca.uhn.fhir.rest.api.Constants;
052import ca.uhn.fhir.rest.api.EncodingEnum;
053import ca.uhn.fhir.rest.api.MethodOutcome;
054import ca.uhn.fhir.rest.api.PatchTypeEnum;
055import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
056import ca.uhn.fhir.rest.api.SummaryEnum;
057import ca.uhn.fhir.rest.api.ValidationModeEnum;
058import ca.uhn.fhir.rest.client.api.IHttpRequest;
059import ca.uhn.fhir.rest.client.method.OperationParameter.IOperationParamConverter;
060import ca.uhn.fhir.rest.param.ParameterUtil;
061import ca.uhn.fhir.rest.param.binder.CollectionBinder;
062import ca.uhn.fhir.util.DateUtils;
063import ca.uhn.fhir.util.ParametersUtil;
064import ca.uhn.fhir.util.ReflectionUtil;
065import ca.uhn.fhir.util.UrlUtil;
066import org.apache.commons.lang3.StringUtils;
067import org.hl7.fhir.instance.model.api.IAnyResource;
068import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
069import org.hl7.fhir.instance.model.api.IBaseResource;
070import org.hl7.fhir.instance.model.api.IIdType;
071
072import java.io.IOException;
073import java.io.InputStream;
074import java.io.PushbackInputStream;
075import java.lang.annotation.Annotation;
076import java.lang.reflect.Method;
077import java.util.ArrayList;
078import java.util.Collection;
079import java.util.Date;
080import java.util.List;
081import java.util.Map;
082import java.util.Map.Entry;
083
084import static org.apache.commons.lang3.StringUtils.isNotBlank;
085
086public class MethodUtil {
087
088        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class);
089
090        /** Non instantiable */
091        private MethodUtil() {
092                // nothing
093        }
094
095        public static void addAcceptHeaderToRequest(
096                        EncodingEnum theEncoding, IHttpRequest theHttpRequest, FhirContext theContext) {
097                if (theEncoding == null) {
098                        if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1) == false) {
099                                theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_LEGACY);
100                        } else {
101                                theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_NON_LEGACY);
102                        }
103                } else if (theEncoding == EncodingEnum.JSON) {
104                        if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1) == false) {
105                                theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON);
106                        } else {
107                                theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY);
108                        }
109                } else if (theEncoding == EncodingEnum.XML) {
110                        if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1) == false) {
111                                theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML);
112                        } else {
113                                theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY);
114                        }
115                }
116        }
117
118        public static HttpGetClientInvocation createConformanceInvocation(FhirContext theContext) {
119                return new HttpGetClientInvocation(theContext, "metadata");
120        }
121
122        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, FhirContext theContext) {
123                return createCreateInvocation(theResource, null, theContext);
124        }
125
126        public static HttpPostClientInvocation createCreateInvocation(
127                        IBaseResource theResource, String theResourceBody, FhirContext theContext) {
128                RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
129                String resourceName = def.getName();
130
131                StringBuilder urlExtension = new StringBuilder();
132                urlExtension.append(resourceName);
133
134                HttpPostClientInvocation retVal;
135                if (StringUtils.isBlank(theResourceBody)) {
136                        retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString());
137                } else {
138                        retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString());
139                }
140
141                retVal.setOmitResourceId(true);
142
143                return retVal;
144        }
145
146        public static HttpPostClientInvocation createCreateInvocation(
147                        IBaseResource theResource,
148                        String theResourceBody,
149                        FhirContext theContext,
150                        Map<String, List<String>> theIfNoneExistParams) {
151                HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theContext);
152                retVal.setIfNoneExistParams(theIfNoneExistParams);
153                return retVal;
154        }
155
156        public static HttpPostClientInvocation createCreateInvocation(
157                        IBaseResource theResource, String theResourceBody, FhirContext theContext, String theIfNoneExistUrl) {
158                HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theContext);
159                retVal.setIfNoneExistString(theIfNoneExistUrl);
160                return retVal;
161        }
162
163        public static HttpPatchClientInvocation createPatchInvocation(
164                        FhirContext theContext, IIdType theId, PatchTypeEnum thePatchType, String theBody) {
165                return PatchMethodBinding.createPatchInvocation(theContext, theId, thePatchType, theBody);
166        }
167
168        public static HttpPatchClientInvocation createPatchInvocation(
169                        FhirContext theContext,
170                        PatchTypeEnum thePatchType,
171                        String theBody,
172                        String theResourceType,
173                        Map<String, List<String>> theMatchParams) {
174                return PatchMethodBinding.createPatchInvocation(
175                                theContext, thePatchType, theBody, theResourceType, theMatchParams);
176        }
177
178        public static HttpPatchClientInvocation createPatchInvocation(
179                        FhirContext theContext, String theUrl, PatchTypeEnum thePatchType, String theBody) {
180                return PatchMethodBinding.createPatchInvocation(theContext, theUrl, thePatchType, theBody);
181        }
182
183        public static HttpPutClientInvocation createUpdateInvocation(
184                        FhirContext theContext,
185                        IBaseResource theResource,
186                        String theResourceBody,
187                        Map<String, List<String>> theMatchParams) {
188                String resourceType = theContext.getResourceType(theResource);
189
190                StringBuilder b = createUrl(resourceType, theMatchParams);
191
192                HttpPutClientInvocation retVal;
193                if (StringUtils.isBlank(theResourceBody)) {
194                        retVal = new HttpPutClientInvocation(theContext, theResource, b.toString());
195                } else {
196                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, b.toString());
197                }
198
199                return retVal;
200        }
201
202        public static HttpPutClientInvocation createUpdateInvocation(
203                        FhirContext theContext, IBaseResource theResource, String theResourceBody, String theMatchUrl) {
204                HttpPutClientInvocation retVal;
205                if (StringUtils.isBlank(theResourceBody)) {
206                        retVal = new HttpPutClientInvocation(theContext, theResource, theMatchUrl);
207                } else {
208                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, theMatchUrl);
209                }
210
211                return retVal;
212        }
213
214        public static HttpPutClientInvocation createUpdateInvocation(
215                        IBaseResource theResource, String theResourceBody, IIdType theId, FhirContext theContext) {
216                String resourceName = theContext.getResourceType(theResource);
217                StringBuilder urlBuilder = new StringBuilder();
218                urlBuilder.append(resourceName);
219                urlBuilder.append('/');
220                urlBuilder.append(theId.getIdPart());
221                String urlExtension = urlBuilder.toString();
222
223                HttpPutClientInvocation retVal;
224                if (StringUtils.isBlank(theResourceBody)) {
225                        retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension);
226                } else {
227                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, urlExtension);
228                }
229
230                retVal.setForceResourceId(theId);
231
232                if (theId.hasVersionIdPart()) {
233                        retVal.addHeader(Constants.HEADER_IF_MATCH, '"' + theId.getVersionIdPart() + '"');
234                }
235
236                return retVal;
237        }
238
239        public static HttpPutClientInvocation createUpdateHistoryRewriteInvocation(
240                        IBaseResource theResource, String theResourceBody, IIdType theId, FhirContext theContext) {
241                String resourceName = theContext.getResourceType(theResource);
242                StringBuilder urlBuilder = new StringBuilder();
243                urlBuilder.append(resourceName);
244                urlBuilder.append('/');
245                urlBuilder.append(theId.getIdPart());
246                if (theId.hasVersionIdPart()) {
247                        urlBuilder.append('/');
248                        urlBuilder.append(Constants.PARAM_HISTORY);
249                        urlBuilder.append('/');
250                        urlBuilder.append(theId.getVersionIdPart());
251                }
252
253                String urlExtension = urlBuilder.toString();
254
255                HttpPutClientInvocation retVal;
256                if (StringUtils.isBlank(theResourceBody)) {
257                        retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension);
258                } else {
259                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, urlExtension);
260                }
261
262                return retVal;
263        }
264
265        public static StringBuilder createUrl(String theResourceType, Map<String, List<String>> theMatchParams) {
266                StringBuilder b = new StringBuilder();
267
268                b.append(theResourceType);
269
270                boolean haveQuestionMark = false;
271                for (Entry<String, List<String>> nextEntry : theMatchParams.entrySet()) {
272                        for (String nextValue : nextEntry.getValue()) {
273                                b.append(haveQuestionMark ? '&' : '?');
274                                haveQuestionMark = true;
275                                b.append(UrlUtil.escapeUrlParam(nextEntry.getKey()));
276                                b.append('=');
277                                b.append(UrlUtil.escapeUrlParam(nextValue));
278                        }
279                }
280                return b;
281        }
282
283        @SuppressWarnings("unchecked")
284        public static List<IParameter> getResourceParameters(
285                        final FhirContext theContext,
286                        Method theMethod,
287                        Object theProvider,
288                        RestOperationTypeEnum theRestfulOperationTypeEnum) {
289                List<IParameter> parameters = new ArrayList<>();
290
291                Class<?>[] parameterTypes = theMethod.getParameterTypes();
292                int paramIndex = 0;
293                for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
294
295                        IParameter param = null;
296                        Class<?> parameterType = parameterTypes[paramIndex];
297                        Class<? extends java.util.Collection<?>> outerCollectionType = null;
298                        Class<? extends java.util.Collection<?>> innerCollectionType = null;
299                        if (TagList.class.isAssignableFrom(parameterType)) {
300                                // TagList is handled directly within the method bindings
301                                param = new NullParameter();
302                        } else {
303                                if (Collection.class.isAssignableFrom(parameterType)) {
304                                        innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
305                                        parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
306                                }
307                                if (Collection.class.isAssignableFrom(parameterType)) {
308                                        outerCollectionType = innerCollectionType;
309                                        innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
310                                        parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
311                                }
312                                if (Collection.class.isAssignableFrom(parameterType)) {
313                                        throw new ConfigurationException(
314                                                        Msg.code(1433) + "Argument #" + paramIndex + " of Method '" + theMethod.getName()
315                                                                        + "' in type '"
316                                                                        + theMethod.getDeclaringClass().getCanonicalName()
317                                                                        + "' is of an invalid generic type (can not be a collection of a collection of a collection)");
318                                }
319                        }
320
321                        if (parameterType.equals(SummaryEnum.class)) {
322                                param = new SummaryEnumParameter();
323                        } else if (parameterType.equals(PatchTypeEnum.class)) {
324                                param = new PatchTypeParameter();
325                        } else {
326                                for (int i = 0; i < annotations.length && param == null; i++) {
327                                        Annotation nextAnnotation = annotations[i];
328
329                                        if (nextAnnotation instanceof RequiredParam) {
330                                                SearchParameter parameter = new SearchParameter();
331                                                parameter.setName(((RequiredParam) nextAnnotation).name());
332                                                parameter.setRequired(true);
333                                                parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes());
334                                                parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes());
335                                                parameter.setChainlists(((RequiredParam) nextAnnotation).chainWhitelist());
336                                                parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
337                                                param = parameter;
338                                        } else if (nextAnnotation instanceof OptionalParam) {
339                                                SearchParameter parameter = new SearchParameter();
340                                                parameter.setName(((OptionalParam) nextAnnotation).name());
341                                                parameter.setRequired(false);
342                                                parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes());
343                                                parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes());
344                                                parameter.setChainlists(((OptionalParam) nextAnnotation).chainWhitelist());
345                                                parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
346                                                param = parameter;
347                                        } else if (nextAnnotation instanceof RawParam) {
348                                                param = new RawParamsParmeter();
349                                        } else if (nextAnnotation instanceof IncludeParam) {
350                                                Class<? extends Collection<Include>> instantiableCollectionType;
351                                                Class<?> specType;
352
353                                                if (parameterType == String.class) {
354                                                        instantiableCollectionType = null;
355                                                        specType = String.class;
356                                                } else if ((parameterType != Include.class)
357                                                                || innerCollectionType == null
358                                                                || outerCollectionType != null) {
359                                                        throw new ConfigurationException(Msg.code(1434) + "Method '" + theMethod.getName()
360                                                                        + "' is annotated with @"
361                                                                        + IncludeParam.class.getSimpleName() + " but has a type other than Collection<"
362                                                                        + Include.class.getSimpleName() + ">");
363                                                } else {
364                                                        instantiableCollectionType = (Class<? extends Collection<Include>>)
365                                                                        CollectionBinder.getInstantiableCollectionType(
366                                                                                        innerCollectionType, "Method '" + theMethod.getName() + "'");
367                                                        specType = parameterType;
368                                                }
369
370                                                param = new IncludeParameter(
371                                                                (IncludeParam) nextAnnotation, instantiableCollectionType, specType);
372                                        } else if (nextAnnotation instanceof ResourceParam) {
373                                                if (IBaseResource.class.isAssignableFrom(parameterType)) {
374                                                        // good
375                                                } else if (String.class.equals(parameterType)) {
376                                                        // good
377                                                } else {
378                                                        StringBuilder b = new StringBuilder();
379                                                        b.append("Method '");
380                                                        b.append(theMethod.getName());
381                                                        b.append("' is annotated with @");
382                                                        b.append(ResourceParam.class.getSimpleName());
383                                                        b.append(" but has a type that is not an implementation of ");
384                                                        b.append(IBaseResource.class.getCanonicalName());
385                                                        throw new ConfigurationException(Msg.code(1435) + b.toString());
386                                                }
387                                                param = new ResourceParameter(parameterType);
388                                        } else if (nextAnnotation instanceof IdParam) {
389                                                param = new NullParameter();
390                                        } else if (nextAnnotation instanceof Elements) {
391                                                param = new ElementsParameter();
392                                        } else if (nextAnnotation instanceof Since) {
393                                                param = new SinceParameter();
394                                                ((SinceParameter) param)
395                                                                .setType(theContext, parameterType, innerCollectionType, outerCollectionType);
396                                        } else if (nextAnnotation instanceof At) {
397                                                param = new AtParameter();
398                                                ((AtParameter) param)
399                                                                .setType(theContext, parameterType, innerCollectionType, outerCollectionType);
400                                        } else if (nextAnnotation instanceof Count) {
401                                                param = new CountParameter();
402                                        } else if (nextAnnotation instanceof Offset) {
403                                                param = new OffsetParameter();
404                                        } else if (nextAnnotation instanceof Sort) {
405                                                param = new SortParameter(theContext);
406                                        } else if (nextAnnotation instanceof TransactionParam) {
407                                                param = new TransactionParameter(theContext);
408                                        } else if (nextAnnotation instanceof ConditionalUrlParam) {
409                                                param = new ConditionalParamBinder(
410                                                                theRestfulOperationTypeEnum, ((ConditionalUrlParam) nextAnnotation).supportsMultiple());
411                                        } else if (nextAnnotation instanceof OperationParam) {
412                                                Operation op = theMethod.getAnnotation(Operation.class);
413                                                param = new OperationParameter(theContext, op.name(), ((OperationParam) nextAnnotation));
414                                        } else if (nextAnnotation instanceof Validate.Mode) {
415                                                if (parameterType.equals(ValidationModeEnum.class) == false) {
416                                                        throw new ConfigurationException(Msg.code(1436) + "Parameter annotated with @"
417                                                                        + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName()
418                                                                        + " must be of type " + ValidationModeEnum.class.getName());
419                                                }
420                                                param = new OperationParameter(
421                                                                                theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1)
422                                                                .setConverter(new IOperationParamConverter() {
423                                                                        @Override
424                                                                        public Object outgoingClient(Object theObject) {
425                                                                                return ParametersUtil.createString(
426                                                                                                theContext, ((ValidationModeEnum) theObject).getCode());
427                                                                        }
428                                                                });
429                                        } else if (nextAnnotation instanceof Validate.Profile) {
430                                                if (parameterType.equals(String.class) == false) {
431                                                        throw new ConfigurationException(Msg.code(1437) + "Parameter annotated with @"
432                                                                        + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName()
433                                                                        + " must be of type " + String.class.getName());
434                                                }
435                                                param = new OperationParameter(
436                                                                                theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1)
437                                                                .setConverter(new IOperationParamConverter() {
438
439                                                                        @Override
440                                                                        public Object outgoingClient(Object theObject) {
441                                                                                return ParametersUtil.createString(theContext, theObject.toString());
442                                                                        }
443                                                                });
444                                        } else {
445                                                continue;
446                                        }
447                                }
448                        }
449
450                        if (param == null) {
451                                throw new ConfigurationException(
452                                                Msg.code(1438) + "Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length)
453                                                                + " of method '" + theMethod.getName() + "' on type '"
454                                                                + theMethod.getDeclaringClass().getCanonicalName()
455                                                                + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
456                        }
457
458                        param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
459                        parameters.add(param);
460
461                        paramIndex++;
462                }
463                return parameters;
464        }
465
466        public static void parseClientRequestResourceHeaders(
467                        IIdType theRequestedId, Map<String, List<String>> theHeaders, IBaseResource resource) {
468                List<String> lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE);
469                if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) {
470                        String headerValue = lmHeaders.get(0);
471                        Date headerDateValue;
472                        try {
473                                headerDateValue = DateUtils.parseDate(headerValue);
474                                if (resource instanceof IResource) {
475                                        IResource iResource = (IResource) resource;
476                                        InstantDt existing = ResourceMetadataKeyEnum.UPDATED.get(iResource);
477                                        if (existing == null || existing.isEmpty()) {
478                                                InstantDt lmValue = new InstantDt(headerDateValue);
479                                                iResource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue);
480                                        }
481                                } else if (resource instanceof IAnyResource) {
482                                        IAnyResource anyResource = (IAnyResource) resource;
483                                        if (anyResource.getMeta().getLastUpdated() == null) {
484                                                anyResource.getMeta().setLastUpdated(headerDateValue);
485                                        }
486                                }
487                        } catch (Exception e) {
488                                ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString());
489                        }
490                }
491
492                List<String> clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC);
493                if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) {
494                        String headerValue = clHeaders.get(0);
495                        if (isNotBlank(headerValue)) {
496                                new IdDt(headerValue).applyTo(resource);
497                        }
498                }
499
500                List<String> locationHeaders = theHeaders.get(Constants.HEADER_LOCATION_LC);
501                if (locationHeaders != null && locationHeaders.size() > 0 && StringUtils.isNotBlank(locationHeaders.get(0))) {
502                        String headerValue = locationHeaders.get(0);
503                        if (isNotBlank(headerValue)) {
504                                new IdDt(headerValue).applyTo(resource);
505                        }
506                }
507
508                IdDt existing = IdDt.of(resource);
509
510                List<String> eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC);
511                String eTagVersion = null;
512                if (eTagHeaders != null && eTagHeaders.size() > 0) {
513                        eTagVersion = ParameterUtil.parseETagValue(eTagHeaders.get(0));
514                }
515                if (isNotBlank(eTagVersion)) {
516                        if (existing == null || existing.isEmpty()) {
517                                if (theRequestedId != null) {
518                                        theRequestedId.withVersion(eTagVersion).applyTo(resource);
519                                }
520                        } else if (existing.hasVersionIdPart() == false) {
521                                existing.withVersion(eTagVersion).applyTo(resource);
522                        }
523                } else if (existing == null || existing.isEmpty()) {
524                        if (theRequestedId != null) {
525                                theRequestedId.applyTo(resource);
526                        }
527                }
528        }
529
530        public static MethodOutcome process2xxResponse(
531                        FhirContext theContext,
532                        int theResponseStatusCode,
533                        String theResponseMimeType,
534                        InputStream theResponseReader,
535                        Map<String, List<String>> theHeaders) {
536                List<String> locationHeaders = new ArrayList<>();
537                List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC);
538                if (lh != null) {
539                        locationHeaders.addAll(lh);
540                }
541                List<String> clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC);
542                if (clh != null) {
543                        locationHeaders.addAll(clh);
544                }
545
546                MethodOutcome retVal = new MethodOutcome();
547                retVal.setResponseStatusCode(theResponseStatusCode);
548                if (locationHeaders.size() > 0) {
549                        String locationHeader = locationHeaders.get(0);
550                        BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader);
551                }
552                if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
553                        EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType);
554                        if (ct != null) {
555                                PushbackInputStream reader = new PushbackInputStream(theResponseReader);
556
557                                try {
558                                        int firstByte = reader.read();
559                                        if (firstByte == -1) {
560                                                BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read");
561                                                reader = null;
562                                        } else {
563                                                reader.unread(firstByte);
564                                        }
565                                } catch (IOException e) {
566                                        BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e);
567                                        reader = null;
568                                }
569
570                                if (reader != null) {
571                                        IParser parser = ct.newParser(theContext);
572                                        IBaseResource outcome = parser.parseResource(reader);
573                                        if (outcome instanceof IBaseOperationOutcome) {
574                                                retVal.setOperationOutcome((IBaseOperationOutcome) outcome);
575                                        } else {
576                                                retVal.setResource(outcome);
577                                        }
578                                }
579
580                        } else {
581                                BaseOutcomeReturningMethodBinding.ourLog.debug(
582                                                "Ignoring response content of type: {}", theResponseMimeType);
583                        }
584                }
585                return retVal;
586        }
587}