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