
ca.uhn.fhir.rest.client.BaseClient Maven / Gradle / Ivy
package ca.uhn.fhir.rest.client;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.method.HttpGetClientInvocation;
import ca.uhn.fhir.rest.method.IClientResponseHandler;
import ca.uhn.fhir.rest.method.IClientResponseHandlerHandlesBinary;
import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.XmlUtil;
public abstract class BaseClient implements IRestfulClient {
/**
* This property is used by unit tests - do not rely on it in production code
* as it may change at any time. If you want to capture responses in a reliable
* way in your own code, just use client interceptors
*/
static final String HAPI_CLIENT_KEEPRESPONSES = "hapi.client.keepresponses";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
private final IHttpClient myClient;
private boolean myDontValidateConformance;
private EncodingEnum myEncoding = null; // default unspecified (will be XML)
private final RestfulClientFactory myFactory;
private List myInterceptors = new ArrayList();
private boolean myKeepResponses = false;
private IHttpResponse myLastResponse;
private String myLastResponseBody;
private Boolean myPrettyPrint = false;
private SummaryEnum mySummary;
private final String myUrlBase;
BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) {
super();
myClient = theClient;
myUrlBase = theUrlBase;
myFactory = theFactory;
/*
* This property is used by unit tests - do not rely on it in production code
* as it may change at any time. If you want to capture responses in a reliable
* way in your own code, just use client interceptors
*/
if ("true".equals(System.getProperty(HAPI_CLIENT_KEEPRESPONSES))) {
setKeepResponses(true);
}
if (XmlUtil.isStaxPresent() == false) {
myEncoding = EncodingEnum.JSON;
}
}
protected Map> createExtraParams() {
HashMap> retVal = new LinkedHashMap>();
if (getEncoding() == EncodingEnum.XML) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
if (isPrettyPrint()) {
retVal.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
}
return retVal;
}
@Override
public T fetchResourceFromUrl(Class theResourceType, String theUrl) {
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
ResourceResponseHandler binding = new ResourceResponseHandler(theResourceType);
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null);
}
void forceConformanceCheck() {
myFactory.validateServerBase(myUrlBase, myClient, this);
}
/**
* Returns the encoding that will be used on requests. Default is null
, which means the client will not
* explicitly request an encoding. (This is standard behaviour according to the FHIR specification)
*/
public EncodingEnum getEncoding() {
return myEncoding;
}
/**
* {@inheritDoc}
*/
@Override
public IHttpClient getHttpClient() {
return myClient;
}
public List getInterceptors() {
return Collections.unmodifiableList(myInterceptors);
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public IHttpResponse getLastResponse() {
return myLastResponse;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public String getLastResponseBody() {
return myLastResponseBody;
}
/**
* {@inheritDoc}
*/
@Override
public String getServerBase() {
return myUrlBase;
}
public SummaryEnum getSummary() {
return mySummary;
}
public String getUrlBase() {
return myUrlBase;
}
T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation) {
return invokeClient(theContext, binding, clientInvocation, false);
}
T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null);
}
T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set theSubsetElements) {
if (!myDontValidateConformance) {
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
}
// TODO: handle non 2xx status codes by throwing the correct exception,
// and ensure it's passed upwards
IHttpRequest httpRequest = null;
IHttpResponse response = null;
try {
Map> params = createExtraParams();
if (clientInvocation instanceof HttpGetClientInvocation) {
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
}
if (theSummaryMode != null) {
params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode()));
} else if (mySummary != null) {
params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode()));
}
if (thePrettyPrint == Boolean.TRUE) {
params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
}
if (theSubsetElements != null && theSubsetElements.isEmpty() == false) {
params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ',')));
}
EncodingEnum encoding = getEncoding();
if (theEncoding != null) {
encoding = theEncoding;
}
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
if (theLogRequestAndResponse) {
ourLog.info("Client invoking: {}", httpRequest);
String body = httpRequest.getRequestBodyFromStream();
if (body != null) {
ourLog.info("Client request body: {}", body);
}
}
for (IClientInterceptor nextInterceptor : myInterceptors) {
nextInterceptor.interceptRequest(httpRequest);
}
response = httpRequest.execute();
for (IClientInterceptor nextInterceptor : myInterceptors) {
nextInterceptor.interceptResponse(response);
}
String mimeType;
if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) {
mimeType = null;
} else {
mimeType = response.getMimeType();
}
Map> headers = response.getAllHeaders();
if (response.getStatus() < 200 || response.getStatus() > 299) {
String body = null;
Reader reader = null;
try {
reader = response.createReader();
body = IOUtils.toString(reader);
} catch (Exception e) {
ourLog.debug("Failed to read input stream", e);
} finally {
IOUtils.closeQuietly(reader);
}
String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo();
IBaseOperationOutcome oo = null;
if (Constants.CT_TEXT.equals(mimeType)) {
message = message + ": " + body;
} else {
EncodingEnum enc = EncodingEnum.forContentType(mimeType);
if (enc != null) {
IParser p = enc.newParser(theContext);
try {
// TODO: handle if something other than OO comes back
oo = (IBaseOperationOutcome) p.parseResource(body);
String details = OperationOutcomeUtil.getFirstIssueDetails(getFhirContext(), oo);
if (isNotBlank(details)) {
message = message + ": " + details;
}
} catch (Exception e) {
ourLog.debug("Failed to process OperationOutcome response");
}
}
}
keepResponseAndLogIt(theLogRequestAndResponse, response, body);
BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatus(), message);
exception.setOperationOutcome(oo);
if (body != null) {
exception.setResponseBody(body);
}
throw exception;
}
if (binding instanceof IClientResponseHandlerHandlesBinary) {
IClientResponseHandlerHandlesBinary handlesBinary = (IClientResponseHandlerHandlesBinary) binding;
if (handlesBinary.isBinary()) {
InputStream reader = response.readEntity();
try {
return handlesBinary.invokeClient(mimeType, reader, response.getStatus(), headers);
} finally {
IOUtils.closeQuietly(reader);
}
}
}
Reader reader = response.createReader();
if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
String responseString = IOUtils.toString(reader);
keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
reader = new StringReader(responseString);
}
try {
return binding.invokeClient(mimeType, reader, response.getStatus(), headers);
} finally {
IOUtils.closeQuietly(reader);
}
} catch (DataFormatException e) {
String msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "failedToParseResponse", httpRequest.getHttpVerbName(), httpRequest.getUri(), e.toString());
throw new FhirClientConnectionException(msg, e);
} catch (IllegalStateException e) {
throw new FhirClientConnectionException(e);
} catch (IOException e) {
String msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "ioExceptionDuringOperation", httpRequest.getHttpVerbName(), httpRequest.getUri(), e.toString());
throw new FhirClientConnectionException(msg, e);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new FhirClientConnectionException(e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public boolean isKeepResponses() {
return myKeepResponses;
}
/**
* Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
* servers which might implement it).
*/
public boolean isPrettyPrint() {
return Boolean.TRUE.equals(myPrettyPrint);
}
private void keepResponseAndLogIt(boolean theLogRequestAndResponse, IHttpResponse response, String responseString) {
if (myKeepResponses) {
myLastResponse = response;
myLastResponseBody = responseString;
}
if (theLogRequestAndResponse) {
String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo();
if (StringUtils.isNotBlank(responseString)) {
ourLog.info("Client response: {}\n{}", message, responseString);
} else {
ourLog.info("Client response: {}", message, responseString);
}
} else {
ourLog.trace("FHIR response:\n{}\n{}", response, responseString);
}
}
@Override
public void registerInterceptor(IClientInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.add(theInterceptor);
}
/**
* This method is an internal part of the HAPI API and may change, use with caution. If you want to disable the
* loading of conformance statements, use
* {@link IRestfulClientFactory#setServerValidationModeEnum(ServerValidationModeEnum)}
*/
public void setDontValidateConformance(boolean theDontValidateConformance) {
myDontValidateConformance = theDontValidateConformance;
}
/**
* Sets the encoding that will be used on requests. Default is null
, which means the client will not
* explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In
* this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
*/
@Override
public void setEncoding(EncodingEnum theEncoding) {
myEncoding = theEncoding;
// return this;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public void setKeepResponses(boolean theKeepResponses) {
myKeepResponses = theKeepResponses;
}
/**
* Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
* servers which might implement it).
*/
@Override
public void setPrettyPrint(Boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
// return this;
}
@Override
public void setSummary(SummaryEnum theSummary) {
mySummary = theSummary;
}
@Override
public void unregisterInterceptor(IClientInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor);
}
static ArrayList> toTypeList(Class extends IBaseResource> thePreferResponseType) {
ArrayList> preferResponseTypes = null;
if (thePreferResponseType != null) {
preferResponseTypes = new ArrayList>(1);
preferResponseTypes.add(thePreferResponseType);
}
return preferResponseTypes;
}
protected final class ResourceResponseHandler implements IClientResponseHandler {
private boolean myAllowHtmlResponse;
private IIdType myId;
private List> myPreferResponseTypes;
private Class myReturnType;
public ResourceResponseHandler() {
this(null);
}
public ResourceResponseHandler(Class theReturnType) {
this(theReturnType, null, null);
}
public ResourceResponseHandler(Class theReturnType, Class extends IBaseResource> thePreferResponseType, IIdType theId) {
this(theReturnType, thePreferResponseType, theId, false);
}
public ResourceResponseHandler(Class theReturnType, Class extends IBaseResource> thePreferResponseType, IIdType theId, boolean theAllowHtmlResponse) {
this(theReturnType, toTypeList(thePreferResponseType), theId, theAllowHtmlResponse);
}
public ResourceResponseHandler(Class theClass, List> thePreferResponseTypes) {
this(theClass, thePreferResponseTypes, null, false);
}
public ResourceResponseHandler(Class theReturnType, List> thePreferResponseTypes, IIdType theId, boolean theAllowHtmlResponse) {
myReturnType = theReturnType;
myId = theId;
myPreferResponseTypes = thePreferResponseTypes;
myAllowHtmlResponse = theAllowHtmlResponse;
}
@Override
public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) {
return readHtmlResponse(theResponseReader);
}
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
}
IParser parser = respType.newParser(getFhirContext());
if (myPreferResponseTypes != null) {
parser.setPreferTypes(myPreferResponseTypes);
}
T retVal = parser.parseResource(myReturnType, theResponseReader);
MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
return retVal;
}
@SuppressWarnings("unchecked")
private T readHtmlResponse(Reader theResponseReader) {
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType);
IBaseResource instance = resDef.newInstance();
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
BaseRuntimeElementCompositeDefinition> textElement = (BaseRuntimeElementCompositeDefinition>) textChild.getChildByName("text");
IBase textInstance = textElement.newInstance();
textChild.getMutator().addValue(instance, textInstance);
BaseRuntimeChildDefinition divChild = textElement.getChildByName("div");
BaseRuntimeElementDefinition> divElement = divChild.getChildByName("div");
IPrimitiveType> divInstance = (IPrimitiveType>) divElement.newInstance();
try {
divInstance.setValueAsString(IOUtils.toString(theResponseReader));
} catch (Exception e) {
throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e);
}
divChild.getMutator().addValue(textInstance, divInstance);
return (T) instance;
}
public void setPreferResponseTypes(List> thePreferResponseTypes) {
myPreferResponseTypes = thePreferResponseTypes;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy