001/*
002 * #%L
003 * HAPI FHIR - Client Framework
004 * %%
005 * Copyright (C) 2014 - 2023 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(EncodingEnum theEncoding, IHttpRequest theHttpRequest,
096                        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
119        public static HttpGetClientInvocation createConformanceInvocation(FhirContext theContext) {
120                return new HttpGetClientInvocation(theContext, "metadata");
121        }
122
123        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, FhirContext theContext) {
124                return createCreateInvocation(theResource, null, theContext);
125        }
126
127        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody,
128                        FhirContext theContext) {
129                RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
130                String resourceName = def.getName();
131
132                StringBuilder urlExtension = new StringBuilder();
133                urlExtension.append(resourceName);
134
135                HttpPostClientInvocation retVal;
136                if (StringUtils.isBlank(theResourceBody)) {
137                        retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString());
138                } else {
139                        retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString());
140                }
141
142                retVal.setOmitResourceId(true);
143
144                return retVal;
145        }
146
147        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody,
148                        FhirContext theContext, Map<String, List<String>> theIfNoneExistParams) {
149                HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theContext);
150                retVal.setIfNoneExistParams(theIfNoneExistParams);
151                return retVal;
152        }
153
154        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody,
155                        FhirContext theContext, String theIfNoneExistUrl) {
156                HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theContext);
157                retVal.setIfNoneExistString(theIfNoneExistUrl);
158                return retVal;
159        }
160
161        public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, IIdType theId,
162                        PatchTypeEnum thePatchType, String theBody) {
163                return PatchMethodBinding.createPatchInvocation(theContext, theId, thePatchType, theBody);
164        }
165
166        public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, PatchTypeEnum thePatchType,
167                        String theBody, String theResourceType, Map<String, List<String>> theMatchParams) {
168                return PatchMethodBinding.createPatchInvocation(theContext, thePatchType, theBody, theResourceType,
169                                theMatchParams);
170        }
171
172        public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, String theUrl,
173                        PatchTypeEnum thePatchType, String theBody) {
174                return PatchMethodBinding.createPatchInvocation(theContext, theUrl, thePatchType, theBody);
175        }
176
177        public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource,
178                        String theResourceBody, Map<String, List<String>> theMatchParams) {
179                String resourceType = theContext.getResourceType(theResource);
180
181                StringBuilder b = createUrl(resourceType, theMatchParams);
182
183                HttpPutClientInvocation retVal;
184                if (StringUtils.isBlank(theResourceBody)) {
185                        retVal = new HttpPutClientInvocation(theContext, theResource, b.toString());
186                } else {
187                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, b.toString());
188                }
189
190                return retVal;
191        }
192
193        public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource,
194                        String theResourceBody, String theMatchUrl) {
195                HttpPutClientInvocation retVal;
196                if (StringUtils.isBlank(theResourceBody)) {
197                        retVal = new HttpPutClientInvocation(theContext, theResource, theMatchUrl);
198                } else {
199                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, theMatchUrl);
200                }
201
202                return retVal;
203        }
204
205        public static HttpPutClientInvocation createUpdateInvocation(IBaseResource theResource, String theResourceBody,
206                                                                                                                                                                         IIdType theId, FhirContext theContext) {
207                String resourceName = theContext.getResourceType(theResource);
208                StringBuilder urlBuilder = new StringBuilder();
209                urlBuilder.append(resourceName);
210                urlBuilder.append('/');
211                urlBuilder.append(theId.getIdPart());
212                String urlExtension = urlBuilder.toString();
213
214                HttpPutClientInvocation retVal;
215                if (StringUtils.isBlank(theResourceBody)) {
216                        retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension);
217                } else {
218                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, urlExtension);
219                }
220
221                retVal.setForceResourceId(theId);
222
223                if (theId.hasVersionIdPart()) {
224                        retVal.addHeader(Constants.HEADER_IF_MATCH, '"' + theId.getVersionIdPart() + '"');
225                }
226
227                return retVal;
228        }
229
230        public static HttpPutClientInvocation createUpdateHistoryRewriteInvocation(IBaseResource theResource, String theResourceBody,
231                                                                                                                                                                                                                IIdType theId, FhirContext theContext) {
232                String resourceName = theContext.getResourceType(theResource);
233                StringBuilder urlBuilder = new StringBuilder();
234                urlBuilder.append(resourceName);
235                urlBuilder.append('/');
236                urlBuilder.append(theId.getIdPart());
237                if (theId.hasVersionIdPart()) {
238                        urlBuilder.append('/');
239                        urlBuilder.append(Constants.PARAM_HISTORY);
240                        urlBuilder.append('/');
241                        urlBuilder.append(theId.getVersionIdPart());
242                }
243
244                String urlExtension = urlBuilder.toString();
245
246                HttpPutClientInvocation retVal;
247                if (StringUtils.isBlank(theResourceBody)) {
248                        retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension);
249                } else {
250                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, urlExtension);
251                }
252
253                return retVal;
254        }
255
256        public static StringBuilder createUrl(String theResourceType, Map<String, List<String>> theMatchParams) {
257                StringBuilder b = new StringBuilder();
258
259                b.append(theResourceType);
260
261                boolean haveQuestionMark = false;
262                for (Entry<String, List<String>> nextEntry : theMatchParams.entrySet()) {
263                        for (String nextValue : nextEntry.getValue()) {
264                                b.append(haveQuestionMark ? '&' : '?');
265                                haveQuestionMark = true;
266                                b.append(UrlUtil.escapeUrlParam(nextEntry.getKey()));
267                                b.append('=');
268                                b.append(UrlUtil.escapeUrlParam(nextValue));
269                        }
270                }
271                return b;
272        }
273
274        @SuppressWarnings("unchecked")
275        public static List<IParameter> getResourceParameters(final FhirContext theContext, Method theMethod,
276                        Object theProvider, RestOperationTypeEnum theRestfulOperationTypeEnum) {
277                List<IParameter> parameters = new ArrayList<>();
278
279                Class<?>[] parameterTypes = theMethod.getParameterTypes();
280                int paramIndex = 0;
281                for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
282
283                        IParameter param = null;
284                        Class<?> parameterType = parameterTypes[paramIndex];
285                        Class<? extends java.util.Collection<?>> outerCollectionType = null;
286                        Class<? extends java.util.Collection<?>> innerCollectionType = null;
287                        if (TagList.class.isAssignableFrom(parameterType)) {
288                                // TagList is handled directly within the method bindings
289                                param = new NullParameter();
290                        } else {
291                                if (Collection.class.isAssignableFrom(parameterType)) {
292                                        innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
293                                        parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
294                                }
295                                if (Collection.class.isAssignableFrom(parameterType)) {
296                                        outerCollectionType = innerCollectionType;
297                                        innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
298                                        parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
299                                }
300                                if (Collection.class.isAssignableFrom(parameterType)) {
301                                        throw new ConfigurationException(Msg.code(1433) + "Argument #" + paramIndex + " of Method '" + theMethod.getName()
302                                                        + "' in type '" + theMethod.getDeclaringClass().getCanonicalName()
303                                                        + "' is of an invalid generic type (can not be a collection of a collection of a collection)");
304                                }
305                        }
306
307                        if (parameterType.equals(SummaryEnum.class)) {
308                                param = new SummaryEnumParameter();
309                        } else if (parameterType.equals(PatchTypeEnum.class)) {
310                                param = new PatchTypeParameter();
311                        } else {
312                                for (int i = 0; i < annotations.length && param == null; i++) {
313                                        Annotation nextAnnotation = annotations[i];
314
315                                        if (nextAnnotation instanceof RequiredParam) {
316                                                SearchParameter parameter = new SearchParameter();
317                                                parameter.setName(((RequiredParam) nextAnnotation).name());
318                                                parameter.setRequired(true);
319                                                parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes());
320                                                parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes());
321                                                parameter.setChainlists(((RequiredParam) nextAnnotation).chainWhitelist());
322                                                parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
323                                                param = parameter;
324                                        } else if (nextAnnotation instanceof OptionalParam) {
325                                                SearchParameter parameter = new SearchParameter();
326                                                parameter.setName(((OptionalParam) nextAnnotation).name());
327                                                parameter.setRequired(false);
328                                                parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes());
329                                                parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes());
330                                                parameter.setChainlists(((OptionalParam) nextAnnotation).chainWhitelist());
331                                                parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
332                                                param = parameter;
333                                        } else if (nextAnnotation instanceof RawParam) {
334                                                param = new RawParamsParmeter();
335                                        } else if (nextAnnotation instanceof IncludeParam) {
336                                                Class<? extends Collection<Include>> instantiableCollectionType;
337                                                Class<?> specType;
338
339                                                if (parameterType == String.class) {
340                                                        instantiableCollectionType = null;
341                                                        specType = String.class;
342                                                } else if ((parameterType != Include.class) || innerCollectionType == null
343                                                                || outerCollectionType != null) {
344                                                        throw new ConfigurationException(Msg.code(1434) + "Method '" + theMethod.getName() + "' is annotated with @"
345                                                                        + IncludeParam.class.getSimpleName() + " but has a type other than Collection<"
346                                                                        + Include.class.getSimpleName() + ">");
347                                                } else {
348                                                        instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder
349                                                                        .getInstantiableCollectionType(innerCollectionType,
350                                                                                        "Method '" + theMethod.getName() + "'");
351                                                        specType = parameterType;
352                                                }
353
354                                                param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType,                                                         specType);
355                                        } else if (nextAnnotation instanceof ResourceParam) {
356                                                if (IBaseResource.class.isAssignableFrom(parameterType)) {
357                                                        // good
358                                                } else if (String.class.equals(parameterType)) {
359                                                        // good
360                                                } else {
361                                                        StringBuilder b = new StringBuilder();
362                                                        b.append("Method '");
363                                                        b.append(theMethod.getName());
364                                                        b.append("' is annotated with @");
365                                                        b.append(ResourceParam.class.getSimpleName());
366                                                        b.append(" but has a type that is not an implementation of ");
367                                                        b.append(IBaseResource.class.getCanonicalName());
368                                                        throw new ConfigurationException(Msg.code(1435) + b.toString());
369                                                }
370                                                param = new ResourceParameter(parameterType);
371                                        } else if (nextAnnotation instanceof IdParam) {
372                                                param = new NullParameter();
373                                        } else if (nextAnnotation instanceof Elements) {
374                                                param = new ElementsParameter();
375                                        } else if (nextAnnotation instanceof Since) {
376                                                param = new SinceParameter();
377                                                ((SinceParameter) param).setType(theContext, parameterType, innerCollectionType,
378                                                                outerCollectionType);
379                                        } else if (nextAnnotation instanceof At) {
380                                                param = new AtParameter();
381                                                ((AtParameter) param).setType(theContext, parameterType, innerCollectionType,
382                                                                outerCollectionType);
383                                        } else if (nextAnnotation instanceof Count) {
384                                                param = new CountParameter();
385                                        } else if (nextAnnotation instanceof Offset) {
386                                                param = new OffsetParameter();
387                                        } else if (nextAnnotation instanceof Sort) {
388                                                param = new SortParameter(theContext);
389                                        } else if (nextAnnotation instanceof TransactionParam) {
390                                                param = new TransactionParameter(theContext);
391                                        } else if (nextAnnotation instanceof ConditionalUrlParam) {
392                                                param = new ConditionalParamBinder(theRestfulOperationTypeEnum,
393                                                                ((ConditionalUrlParam) nextAnnotation).supportsMultiple());
394                                        } else if (nextAnnotation instanceof OperationParam) {
395                                                Operation op = theMethod.getAnnotation(Operation.class);
396                                                param = new OperationParameter(theContext, op.name(), ((OperationParam) nextAnnotation));
397                                        } else if (nextAnnotation instanceof Validate.Mode) {
398                                                if (parameterType.equals(ValidationModeEnum.class) == false) {
399                                                        throw new ConfigurationException(Msg.code(1436) + "Parameter annotated with @"
400                                                                        + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName()
401                                                                        + " must be of type " + ValidationModeEnum.class.getName());
402                                                }
403                                                param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE,
404                                                                Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IOperationParamConverter() {
405                                                                        @Override
406                                                                        public Object outgoingClient(Object theObject) {
407                                                                                return ParametersUtil.createString(theContext,
408                                                                                                ((ValidationModeEnum) theObject).getCode());
409                                                                        }
410                                                                });
411                                        } else if (nextAnnotation instanceof Validate.Profile) {
412                                                if (parameterType.equals(String.class) == false) {
413                                                        throw new ConfigurationException(Msg.code(1437) + "Parameter annotated with @"
414                                                                        + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName()
415                                                                        + " must be of type " + String.class.getName());
416                                                }
417                                                param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE,
418                                                                Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IOperationParamConverter() {
419
420                                                                        @Override
421                                                                        public Object outgoingClient(Object theObject) {
422                                                                                return ParametersUtil.createString(theContext, theObject.toString());
423                                                                        }
424                                                                });
425                                        } else {
426                                                continue;
427                                        }
428
429                                }
430
431                        }
432
433                        if (param == null) {
434                                throw new ConfigurationException(Msg.code(1438) + "Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length)
435                                                + " of method '" + theMethod.getName() + "' on type '"
436                                                + theMethod.getDeclaringClass().getCanonicalName()
437                                                + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
438                        }
439
440                        param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
441                        parameters.add(param);
442
443                        paramIndex++;
444                }
445                return parameters;
446        }
447
448        public static void parseClientRequestResourceHeaders(IIdType theRequestedId, Map<String, List<String>> theHeaders,
449                        IBaseResource resource) {
450                List<String> lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE);
451                if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) {
452                        String headerValue = lmHeaders.get(0);
453                        Date headerDateValue;
454                        try {
455                                headerDateValue = DateUtils.parseDate(headerValue);
456                                if (resource instanceof IResource) {
457                                        IResource iResource = (IResource) resource;
458                                        InstantDt existing = ResourceMetadataKeyEnum.UPDATED.get(iResource);
459                                        if (existing == null || existing.isEmpty()) {
460                                                InstantDt lmValue = new InstantDt(headerDateValue);
461                                                iResource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue);
462                                        }
463                                } else if (resource instanceof IAnyResource) {
464                                        IAnyResource anyResource = (IAnyResource) resource;
465                                        if (anyResource.getMeta().getLastUpdated() == null) {
466                                                anyResource.getMeta().setLastUpdated(headerDateValue);
467                                        }
468                                }
469                        } catch (Exception e) {
470                                ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString());
471                        }
472                }
473
474                List<String> clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC);
475                if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) {
476                        String headerValue = clHeaders.get(0);
477                        if (isNotBlank(headerValue)) {
478                                new IdDt(headerValue).applyTo(resource);
479                        }
480                }
481
482                List<String> locationHeaders = theHeaders.get(Constants.HEADER_LOCATION_LC);
483                if (locationHeaders != null && locationHeaders.size() > 0 && StringUtils.isNotBlank(locationHeaders.get(0))) {
484                        String headerValue = locationHeaders.get(0);
485                        if (isNotBlank(headerValue)) {
486                                new IdDt(headerValue).applyTo(resource);
487                        }
488                }
489
490                IdDt existing = IdDt.of(resource);
491
492                List<String> eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC);
493                String eTagVersion = null;
494                if (eTagHeaders != null && eTagHeaders.size() > 0) {
495                        eTagVersion = ParameterUtil.parseETagValue(eTagHeaders.get(0));
496                }
497                if (isNotBlank(eTagVersion)) {
498                        if (existing == null || existing.isEmpty()) {
499                                if (theRequestedId != null) {
500                                        theRequestedId.withVersion(eTagVersion).applyTo(resource);
501                                }
502                        } else if (existing.hasVersionIdPart() == false) {
503                                existing.withVersion(eTagVersion).applyTo(resource);
504                        }
505                } else if (existing == null || existing.isEmpty()) {
506                        if (theRequestedId != null) {
507                                theRequestedId.applyTo(resource);
508                        }
509                }
510
511        }
512
513        public static MethodOutcome process2xxResponse(FhirContext theContext, int theResponseStatusCode,
514                        String theResponseMimeType, InputStream theResponseReader, Map<String, List<String>> theHeaders) {
515                List<String> locationHeaders = new ArrayList<>();
516                List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC);
517                if (lh != null) {
518                        locationHeaders.addAll(lh);
519                }
520                List<String> clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC);
521                if (clh != null) {
522                        locationHeaders.addAll(clh);
523                }
524
525                MethodOutcome retVal = new MethodOutcome();
526                if (locationHeaders.size() > 0) {
527                        String locationHeader = locationHeaders.get(0);
528                        BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader);
529                }
530                if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
531                        EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType);
532                        if (ct != null) {
533                                PushbackInputStream reader = new PushbackInputStream(theResponseReader);
534
535                                try {
536                                        int firstByte = reader.read();
537                                        if (firstByte == -1) {
538                                                BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read");
539                                                reader = null;
540                                        } else {
541                                                reader.unread(firstByte);
542                                        }
543                                } catch (IOException e) {
544                                        BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e);
545                                        reader = null;
546                                }
547
548                                if (reader != null) {
549                                        IParser parser = ct.newParser(theContext);
550                                        IBaseResource outcome = parser.parseResource(reader);
551                                        if (outcome instanceof IBaseOperationOutcome) {
552                                                retVal.setOperationOutcome((IBaseOperationOutcome) outcome);
553                                        } else {
554                                                retVal.setResource(outcome);
555                                        }
556                                }
557
558                        } else {
559                                BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}",
560                                                theResponseMimeType);
561                        }
562                }
563                return retVal;
564        }
565
566}