001package ca.uhn.fhir.rest.client.impl;
002
003/*
004 * #%L
005 * HAPI FHIR - Client Framework
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
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 */
022
023import ca.uhn.fhir.context.*;
024import ca.uhn.fhir.interceptor.api.HookParams;
025import ca.uhn.fhir.interceptor.api.IInterceptorService;
026import ca.uhn.fhir.interceptor.api.Pointcut;
027import ca.uhn.fhir.interceptor.executor.InterceptorService;
028import ca.uhn.fhir.parser.DataFormatException;
029import ca.uhn.fhir.parser.IParser;
030import ca.uhn.fhir.rest.api.*;
031import ca.uhn.fhir.rest.client.api.*;
032import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
033import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
034import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
035import ca.uhn.fhir.rest.client.interceptor.AdditionalRequestHeadersInterceptor;
036import ca.uhn.fhir.rest.client.method.HttpGetClientInvocation;
037import ca.uhn.fhir.rest.client.method.IClientResponseHandler;
038import ca.uhn.fhir.rest.client.method.IClientResponseHandlerHandlesBinary;
039import ca.uhn.fhir.rest.client.method.MethodUtil;
040import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
041import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
042import ca.uhn.fhir.util.BinaryUtil;
043import ca.uhn.fhir.util.OperationOutcomeUtil;
044import ca.uhn.fhir.util.XmlDetectionUtil;
045import com.google.common.base.Charsets;
046import org.apache.commons.io.IOUtils;
047import org.apache.commons.lang3.StringUtils;
048import org.apache.commons.lang3.Validate;
049import org.hl7.fhir.instance.model.api.*;
050
051import javax.annotation.Nonnull;
052import java.io.ByteArrayInputStream;
053import java.io.IOException;
054import java.io.InputStream;
055import java.io.Reader;
056import java.util.*;
057
058import static org.apache.commons.lang3.StringUtils.isBlank;
059import static org.apache.commons.lang3.StringUtils.isNotBlank;
060
061public abstract class BaseClient implements IRestfulClient {
062
063        /**
064         * This property is used by unit tests - do not rely on it in production code
065         * as it may change at any time. If you want to capture responses in a reliable
066         * way in your own code, just use client interceptors
067         */
068        public static final String HAPI_CLIENT_KEEPRESPONSES = "hapi.client.keepresponses";
069
070        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
071
072        private final IHttpClient myClient;
073        private final RestfulClientFactory myFactory;
074        private final String myUrlBase;
075        private boolean myDontValidateConformance;
076        private EncodingEnum myEncoding = null; // default unspecified (will be JSON)
077        private boolean myKeepResponses = false;
078        private IHttpResponse myLastResponse;
079        private String myLastResponseBody;
080        private Boolean myPrettyPrint = false;
081        private SummaryEnum mySummary;
082        private RequestFormatParamStyleEnum myRequestFormatParamStyle = RequestFormatParamStyleEnum.SHORT;
083        private IInterceptorService myInterceptorService;
084
085        BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) {
086                super();
087                myClient = theClient;
088                myUrlBase = theUrlBase;
089                myFactory = theFactory;
090
091                /*
092                 * This property is used by unit tests - do not rely on it in production code
093                 * as it may change at any time. If you want to capture responses in a reliable
094                 * way in your own code, just use client interceptors
095                 */
096                if ("true".equals(System.getProperty(HAPI_CLIENT_KEEPRESPONSES))) {
097                        setKeepResponses(true);
098                }
099
100                if (XmlDetectionUtil.isStaxPresent() == false) {
101                        myEncoding = EncodingEnum.JSON;
102                }
103
104                setInterceptorService(new InterceptorService());
105        }
106
107        @Override
108        public IInterceptorService getInterceptorService() {
109                return myInterceptorService;
110        }
111
112        @Override
113        public void setInterceptorService(@Nonnull IInterceptorService theInterceptorService) {
114                Validate.notNull(theInterceptorService, "theInterceptorService must not be null");
115                myInterceptorService = theInterceptorService;
116        }
117
118        protected Map<String, List<String>> createExtraParams(String theCustomAcceptHeader) {
119                HashMap<String, List<String>> retVal = new LinkedHashMap<>();
120
121                if (isBlank(theCustomAcceptHeader)) {
122                        if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
123                                if (getEncoding() == EncodingEnum.XML) {
124                                        retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
125                                } else if (getEncoding() == EncodingEnum.JSON) {
126                                        retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
127                                }
128                        }
129                }
130
131                if (isPrettyPrint()) {
132                        retVal.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
133                }
134
135                return retVal;
136        }
137
138        @Override
139        public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
140                BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
141                ResourceResponseHandler<T> binding = new ResourceResponseHandler<>(theResourceType);
142                return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null, null, null);
143        }
144
145        void forceConformanceCheck() {
146                myFactory.validateServerBase(myUrlBase, myClient, this);
147        }
148
149        @Override
150        public EncodingEnum getEncoding() {
151                return myEncoding;
152        }
153
154        /**
155         * Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not
156         * explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In
157         * this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
158         */
159        @Override
160        public void setEncoding(EncodingEnum theEncoding) {
161                myEncoding = theEncoding;
162                // return this;
163        }
164
165        /**
166         * {@inheritDoc}
167         */
168        @Override
169        public IHttpClient getHttpClient() {
170                return myClient;
171        }
172
173        /**
174         * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
175         */
176        public IHttpResponse getLastResponse() {
177                return myLastResponse;
178        }
179
180        /**
181         * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
182         */
183        public String getLastResponseBody() {
184                return myLastResponseBody;
185        }
186
187        /**
188         * {@inheritDoc}
189         */
190        @Override
191        public String getServerBase() {
192                return myUrlBase;
193        }
194
195        public SummaryEnum getSummary() {
196                return mySummary;
197        }
198
199        @Override
200        public void setSummary(SummaryEnum theSummary) {
201                mySummary = theSummary;
202        }
203
204        public String getUrlBase() {
205                return myUrlBase;
206        }
207
208        @Override
209        public void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle) {
210                Validate.notNull(theRequestFormatParamStyle, "theRequestFormatParamStyle must not be null");
211                myRequestFormatParamStyle = theRequestFormatParamStyle;
212        }
213
214        protected <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation) {
215                return invokeClient(theContext, binding, clientInvocation, false);
216        }
217
218        protected <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
219                return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null, null, null);
220        }
221
222        protected <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
223                                                         boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective, String theCustomAcceptHeader,
224                                                         Map<String, List<String>> theCustomHeaders) {
225
226                if (!myDontValidateConformance) {
227                        myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
228                }
229
230                // TODO: handle non 2xx status codes by throwing the correct exception,
231                // and ensure it's passed upwards
232                IHttpRequest httpRequest = null;
233                IHttpResponse response = null;
234                try {
235                        Map<String, List<String>> params = createExtraParams(theCustomAcceptHeader);
236
237                        if (clientInvocation instanceof HttpGetClientInvocation) {
238                                if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT && isBlank(theCustomAcceptHeader)) {
239                                        if (theEncoding == EncodingEnum.XML) {
240                                                params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
241                                        } else if (theEncoding == EncodingEnum.JSON) {
242                                                params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
243                                        }
244                                }
245                        }
246
247                        if (theSummaryMode != null) {
248                                params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode()));
249                        } else if (mySummary != null) {
250                                params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode()));
251                        }
252
253                        if (thePrettyPrint == Boolean.TRUE) {
254                                params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
255                        }
256
257                        if (theSubsetElements != null && theSubsetElements.isEmpty() == false) {
258                                params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ',')));
259                        }
260
261                        EncodingEnum encoding = getEncoding();
262                        if (theEncoding != null) {
263                                encoding = theEncoding;
264                        }
265
266                        httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
267
268                        if (isNotBlank(theCustomAcceptHeader)) {
269                                httpRequest.removeHeaders(Constants.HEADER_ACCEPT);
270                                httpRequest.addHeader(Constants.HEADER_ACCEPT, theCustomAcceptHeader);
271                        }
272
273                        if (theCacheControlDirective != null) {
274                                StringBuilder b = new StringBuilder();
275                                addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache());
276                                addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore());
277                                if (theCacheControlDirective.getMaxResults() != null) {
278                                        addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS + "=" + theCacheControlDirective.getMaxResults().intValue(), true);
279                                }
280                                if (b.length() > 0) {
281                                        httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString());
282                                }
283                        }
284
285                        if (theLogRequestAndResponse) {
286                                ourLog.info("Client invoking: {}", httpRequest);
287                                String body = httpRequest.getRequestBodyFromStream();
288                                if (body != null) {
289                                        ourLog.info("Client request body: {}", body);
290                                }
291                        }
292
293                        if (theCustomHeaders != null) {
294                                AdditionalRequestHeadersInterceptor interceptor = new AdditionalRequestHeadersInterceptor(theCustomHeaders);
295                                interceptor.interceptRequest(httpRequest);
296                        }
297
298                        HookParams requestParams = new HookParams();
299                        requestParams.add(IHttpRequest.class, httpRequest);
300                        requestParams.add(IRestfulClient.class, this);
301                        getInterceptorService().callHooks(Pointcut.CLIENT_REQUEST, requestParams);
302
303                        response = httpRequest.execute();
304
305                        HookParams responseParams = new HookParams();
306                        responseParams.add(IHttpRequest.class, httpRequest);
307                        responseParams.add(IHttpResponse.class, response);
308                        responseParams.add(IRestfulClient.class, this);
309                        getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams);
310
311                        String mimeType;
312                        if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) {
313                                mimeType = null;
314                        } else {
315                                mimeType = response.getMimeType();
316                        }
317
318                        Map<String, List<String>> headers = response.getAllHeaders();
319
320                        if (response.getStatus() < 200 || response.getStatus() > 299) {
321                                String body = null;
322                                try (Reader reader = response.createReader()) {
323                                        body = IOUtils.toString(reader);
324                                } catch (Exception e) {
325                                        ourLog.debug("Failed to read input stream", e);
326                                }
327
328                                String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo();
329                                IBaseOperationOutcome oo = null;
330                                if (Constants.CT_TEXT.equals(mimeType)) {
331                                        message = message + ": " + body;
332                                } else {
333                                        EncodingEnum enc = EncodingEnum.forContentType(mimeType);
334                                        if (enc != null) {
335                                                IParser p = enc.newParser(theContext);
336                                                try {
337                                                        // TODO: handle if something other than OO comes back
338                                                        oo = (IBaseOperationOutcome) p.parseResource(body);
339                                                        String details = OperationOutcomeUtil.getFirstIssueDetails(getFhirContext(), oo);
340                                                        if (isNotBlank(details)) {
341                                                                message = message + ": " + details;
342                                                        }
343                                                } catch (Exception e) {
344                                                        ourLog.debug("Failed to process OperationOutcome response");
345                                                }
346                                        }
347                                }
348
349                                keepResponseAndLogIt(theLogRequestAndResponse, response, body);
350
351                                BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatus(), message);
352                                exception.setOperationOutcome(oo);
353
354                                if (body != null) {
355                                        exception.setResponseBody(body);
356                                }
357
358                                throw exception;
359                        }
360                        if (binding instanceof IClientResponseHandlerHandlesBinary) {
361                                IClientResponseHandlerHandlesBinary<T> handlesBinary = (IClientResponseHandlerHandlesBinary<T>) binding;
362                                if (handlesBinary.isBinary()) {
363                                        try (InputStream reader = response.readEntity()) {
364                                                return handlesBinary.invokeClientForBinary(mimeType, reader, response.getStatus(), headers);
365                                        }
366                                }
367                        }
368
369                        try (InputStream inputStream = response.readEntity()) {
370                                InputStream inputStreamToReturn = inputStream;
371
372                                if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
373                                        if (inputStream != null) {
374                                                String responseString = IOUtils.toString(inputStream, Charsets.UTF_8);
375                                                keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
376                                                inputStreamToReturn = new ByteArrayInputStream(responseString.getBytes(Charsets.UTF_8));
377                                        }
378                                }
379
380                                if (inputStreamToReturn == null) {
381                                        inputStreamToReturn = new ByteArrayInputStream(new byte[]{});
382                                }
383
384                                return binding.invokeClient(mimeType, inputStreamToReturn, response.getStatus(), headers);
385                        }
386
387                } catch (DataFormatException e) {
388                        String msg;
389                        if (httpRequest != null) {
390                                msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "failedToParseResponse", httpRequest.getHttpVerbName(), httpRequest.getUri(), e.toString());
391                        } else {
392                                msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "failedToParseResponse", "UNKNOWN", "UNKNOWN", e.toString());
393                        }
394                        throw new FhirClientConnectionException(msg, e);
395                } catch (IllegalStateException e) {
396                        throw new FhirClientConnectionException(e);
397                } catch (IOException e) {
398                        String msg;
399                        msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "failedToParseResponse", httpRequest.getHttpVerbName(), httpRequest.getUri(), e.toString());
400                        throw new FhirClientConnectionException(msg, e);
401                } catch (RuntimeException e) {
402                        throw e;
403                } catch (Exception e) {
404                        throw new FhirClientConnectionException(e);
405                } finally {
406                        if (response != null) {
407                                response.close();
408                        }
409                }
410        }
411
412        private void addToCacheControlHeader(StringBuilder theBuilder, String theDirective, boolean theActive) {
413                if (theActive) {
414                        if (theBuilder.length() > 0) {
415                                theBuilder.append(", ");
416                        }
417                        theBuilder.append(theDirective);
418                }
419        }
420
421        /**
422         * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
423         */
424        public boolean isKeepResponses() {
425                return myKeepResponses;
426        }
427
428        /**
429         * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
430         */
431        public void setKeepResponses(boolean theKeepResponses) {
432                myKeepResponses = theKeepResponses;
433        }
434
435        /**
436         * Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
437         * that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
438         * servers which might implement it).
439         */
440        public boolean isPrettyPrint() {
441                return Boolean.TRUE.equals(myPrettyPrint);
442        }
443
444        /**
445         * Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
446         * that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
447         * servers which might implement it).
448         */
449        @Override
450        public void setPrettyPrint(Boolean thePrettyPrint) {
451                myPrettyPrint = thePrettyPrint;
452                // return this;
453        }
454
455        private void keepResponseAndLogIt(boolean theLogRequestAndResponse, IHttpResponse response, String responseString) {
456                if (myKeepResponses) {
457                        myLastResponse = response;
458                        myLastResponseBody = responseString;
459                }
460                if (theLogRequestAndResponse) {
461                        String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo();
462                        if (StringUtils.isNotBlank(responseString)) {
463                                ourLog.info("Client response: {}\n{}", message, responseString);
464                        } else {
465                                ourLog.info("Client response: {}", message);
466                        }
467                } else {
468                        ourLog.trace("FHIR response:\n{}\n{}", response, responseString);
469                }
470        }
471
472        @Override
473        public void registerInterceptor(Object theInterceptor) {
474                Validate.notNull(theInterceptor, "Interceptor can not be null");
475                getInterceptorService().registerInterceptor(theInterceptor);
476        }
477
478        /**
479         * This method is an internal part of the HAPI API and may change, use with caution. If you want to disable the
480         * loading of conformance statements, use
481         * {@link IRestfulClientFactory#setServerValidationMode(ServerValidationModeEnum)}
482         */
483        public void setDontValidateConformance(boolean theDontValidateConformance) {
484                myDontValidateConformance = theDontValidateConformance;
485        }
486
487        @Override
488        public void unregisterInterceptor(Object theInterceptor) {
489                Validate.notNull(theInterceptor, "Interceptor can not be null");
490                getInterceptorService().unregisterInterceptor(theInterceptor);
491        }
492
493        protected final class ResourceOrBinaryResponseHandler extends ResourceResponseHandler<IBaseResource> {
494
495
496                @Override
497                public IBaseResource invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
498
499                        /*
500                         * For operation responses, if the response content type is a FHIR content-type
501                         * (which is will probably almost always be) we just handle it normally. However,
502                         * if we get back a successful (2xx) response from an operation, and the content
503                         * type is something other than FHIR, we'll return it as a Binary wrapped in
504                         * a Parameters resource.
505                         */
506                        EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
507                        if (respType != null || theResponseStatusCode < 200 || theResponseStatusCode >= 300) {
508                                return super.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
509                        }
510
511                        // Create a Binary resource to return
512                        IBaseBinary responseBinary = BinaryUtil.newBinary(getFhirContext());
513
514                        // Fetch the content type
515                        String contentType = null;
516                        List<String> contentTypeHeaders = theHeaders.get(Constants.HEADER_CONTENT_TYPE_LC);
517                        if (contentTypeHeaders != null && contentTypeHeaders.size() > 0) {
518                                contentType = contentTypeHeaders.get(0);
519                        }
520                        responseBinary.setContentType(contentType);
521
522                        // Fetch the content itself
523                        try {
524                                responseBinary.setContent(IOUtils.toByteArray(theResponseInputStream));
525                        } catch (IOException e) {
526                                throw new InternalErrorException("IO failure parsing response", e);
527                        }
528
529                        return responseBinary;
530                }
531
532        }
533
534        protected class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
535
536                private boolean myAllowHtmlResponse;
537                private IIdType myId;
538                private List<Class<? extends IBaseResource>> myPreferResponseTypes;
539                private Class<T> myReturnType;
540
541                public ResourceResponseHandler() {
542                        this(null);
543                }
544
545                public ResourceResponseHandler(Class<T> theReturnType) {
546                        this(theReturnType, null, null);
547                }
548
549                public ResourceResponseHandler(Class<T> theReturnType, Class<? extends IBaseResource> thePreferResponseType, IIdType theId) {
550                        this(theReturnType, thePreferResponseType, theId, false);
551                }
552
553                public ResourceResponseHandler(Class<T> theReturnType, Class<? extends IBaseResource> thePreferResponseType, IIdType theId, boolean theAllowHtmlResponse) {
554                        this(theReturnType, toTypeList(thePreferResponseType), theId, theAllowHtmlResponse);
555                }
556
557                public ResourceResponseHandler(Class<T> theClass, List<Class<? extends IBaseResource>> thePreferResponseTypes) {
558                        this(theClass, thePreferResponseTypes, null, false);
559                }
560
561                public ResourceResponseHandler(Class<T> theReturnType, List<Class<? extends IBaseResource>> thePreferResponseTypes, IIdType theId, boolean theAllowHtmlResponse) {
562                        myReturnType = theReturnType;
563                        myId = theId;
564                        myPreferResponseTypes = thePreferResponseTypes;
565                        myAllowHtmlResponse = theAllowHtmlResponse;
566                }
567
568                @Override
569                public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
570                        if (theResponseStatusCode == Constants.STATUS_HTTP_204_NO_CONTENT) {
571                                return null;
572                        }
573
574                        EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
575                        if (respType == null) {
576                                if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) {
577                                        return readHtmlResponse(theResponseInputStream);
578                                }
579                                throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
580                        }
581                        IParser parser = respType.newParser(getFhirContext());
582                        parser.setServerBaseUrl(getUrlBase());
583                        if (myPreferResponseTypes != null) {
584                                parser.setPreferTypes(myPreferResponseTypes);
585                        }
586                        T retVal = parser.parseResource(myReturnType, theResponseInputStream);
587
588                        MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
589
590                        return retVal;
591                }
592
593                @SuppressWarnings("unchecked")
594                private T readHtmlResponse(InputStream theResponseInputStream) {
595                        RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType);
596                        IBaseResource instance = resDef.newInstance();
597                        BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
598                        BaseRuntimeElementCompositeDefinition<?> textElement = (BaseRuntimeElementCompositeDefinition<?>) textChild.getChildByName("text");
599                        IBase textInstance = textElement.newInstance();
600                        textChild.getMutator().addValue(instance, textInstance);
601
602                        BaseRuntimeChildDefinition divChild = textElement.getChildByName("div");
603                        BaseRuntimeElementDefinition<?> divElement = divChild.getChildByName("div");
604                        IPrimitiveType<?> divInstance = (IPrimitiveType<?>) divElement.newInstance();
605                        try {
606                                divInstance.setValueAsString(IOUtils.toString(theResponseInputStream, Charsets.UTF_8));
607                        } catch (Exception e) {
608                                throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e);
609                        }
610                        divChild.getMutator().addValue(textInstance, divInstance);
611                        return (T) instance;
612                }
613
614                public ResourceResponseHandler<T> setPreferResponseTypes(List<Class<? extends IBaseResource>> thePreferResponseTypes) {
615                        myPreferResponseTypes = thePreferResponseTypes;
616                        return this;
617                }
618        }
619
620        static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) {
621                ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null;
622                if (thePreferResponseType != null) {
623                        preferResponseTypes = new ArrayList<>(1);
624                        preferResponseTypes.add(thePreferResponseType);
625                }
626                return preferResponseTypes;
627        }
628
629}