
com.sap.cloud.sdk.s4hana.connectivity.HttpRequestExecutor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of connectivity Show documentation
Show all versions of connectivity Show documentation
Connectivity to SAP S/4HANA.
The newest version!
/*
* Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.cloud.sdk.s4hana.connectivity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.zip.GZIPOutputStream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.RequestLine;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.slf4j.Logger;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.sap.cloud.sdk.cloudplatform.auditlog.AccessedAttribute;
import com.sap.cloud.sdk.cloudplatform.auditlog.AuditLogger;
import com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataObject;
import com.sap.cloud.sdk.cloudplatform.auditlog.AuditedDataSubject;
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.Header;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpEntityUtil;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.cloudplatform.security.Authorization;
import com.sap.cloud.sdk.s4hana.connectivity.exception.AccessDeniedException;
import com.sap.cloud.sdk.s4hana.connectivity.exception.CloudConnectorException;
import com.sap.cloud.sdk.s4hana.connectivity.exception.LogonErrorException;
import com.sap.cloud.sdk.s4hana.connectivity.exception.MissingConfigException;
import com.sap.cloud.sdk.s4hana.connectivity.exception.QueryExecutionException;
import com.sap.cloud.sdk.s4hana.connectivity.exception.QuerySerializationException;
import com.sap.cloud.sdk.s4hana.serialization.SapClient;
import lombok.Getter;
import lombok.Value;
/**
* A collection of methods which are commonly called during executions of a query.
*
* @param
* The type of the query to execute.
* @param
* The type of the result to return.
*/
public class HttpRequestExecutor, QueryResultT extends QueryResult>
{
private static final Logger logger = CloudLoggerFactory.getLogger(HttpRequestExecutor.class);
@Getter
private final QueryExecutionMeasurements measurements = new QueryExecutionMeasurements();
private static class ErpAuthorization extends Authorization
{
ErpAuthorization( @Nonnull final String missingAuthorization )
{
super(missingAuthorization);
}
}
/**
* A helper class to wrap request body and headers.
*/
@Value
static final class RequestBodyWithHeader
{
private final List headers;
private final String body;
}
private void assertValidConfigContext( final ErpConfigContext configContext )
throws MissingConfigException
{
if( configContext.getDestinationName() == null ) {
throw new MissingConfigException(
"No destination name configured (is null). "
+ "Please provide a destination name in "
+ ErpConfigContext.class.getSimpleName()
+ ".");
}
if( configContext.getSapClient() == null ) {
throw new MissingConfigException(
"No "
+ SapClient.class.getSimpleName()
+ " configured (is null). "
+ "Please set a "
+ SapClient.class.getSimpleName()
+ " in property '"
+ ErpConfigContext.DEFAULT_SAP_CLIENT_PROPERTY
+ "' of destination '"
+ configContext.getDestinationName()
+ "' or provide the "
+ SapClient.class.getSimpleName()
+ " as an explicit argument of "
+ ErpConfigContext.class.getSimpleName()
+ ".");
}
if( configContext.getLocale() == null ) {
throw new MissingConfigException(
"No "
+ Locale.class.getSimpleName()
+ " configured (is null). "
+ "Please set a "
+ Locale.class.getSimpleName()
+ " in property '"
+ ErpConfigContext.DEFAULT_LOCALE_PROPERTY
+ "' of destination '"
+ configContext.getDestinationName()
+ "' or provide the "
+ Locale.class.getSimpleName()
+ " as an explicit argument of "
+ ErpConfigContext.class.getSimpleName()
+ ".");
}
}
private static final int MAX_UNCOMPRESSED_PAYLOAD_LENGTH = 1400;
@Nonnull
private ByteArrayEntity getBodyAsCompressedEntity( @Nonnull final String body )
throws QuerySerializationException
{
final ByteArrayEntity entity;
final byte[] content;
try {
content = body.getBytes(StandardCharsets.UTF_8.toString());
}
catch( final UnsupportedEncodingException e ) {
throw new QuerySerializationException("Failed to to convert payload from String to UTF8 byte[].", e);
}
if( content.length > MAX_UNCOMPRESSED_PAYLOAD_LENGTH ) {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try( final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream) ) {
gzipOutputStream.write(content);
}
catch( final IOException e ) {
throw new QuerySerializationException("Failed to write to GZIP-compressed stream.", e);
}
entity = new ByteArrayEntity(outputStream.toByteArray());
entity.setContentEncoding("gzip");
if( logger.isInfoEnabled() ) {
logger.info(
"Compressed length of ERP query body: "
+ entity.getContentLength()
+ " bytes, was "
+ content.length
+ " bytes.");
}
} else {
entity = new ByteArrayEntity(content);
entity.setContentEncoding(StandardCharsets.UTF_8.toString());
if( logger.isInfoEnabled() ) {
logger.info("Length of ERP query body: " + entity.getContentLength() + " bytes.");
}
}
return entity;
}
@Nonnull
private URI appendQueryParam(
@Nonnull final URI uri,
@Nullable final String queryParamKey,
@Nullable final String queryParamValue )
throws QuerySerializationException
{
final String query = uri.getQuery();
final String queryParam = queryParamKey + "=" + queryParamValue;
final String newQuery;
if( Strings.isNullOrEmpty(query) ) {
newQuery = queryParam;
} else {
newQuery = query + "&" + queryParam;
}
try {
return new URI(
uri.getScheme(),
uri.getUserInfo(),
uri.getHost(),
uri.getPort(),
uri.getPath(),
newQuery,
uri.getFragment());
}
catch( final URISyntaxException e ) {
throw new QuerySerializationException(e);
}
}
private void handleHttpStatus(
@Nonnull final ErpConfigContext configContext,
final int statusCode,
@Nullable final String responseBody,
@Nonnull final List responseHeaders )
throws QueryExecutionException
{
if( statusCode == HttpStatus.SC_OK ) {
if( logger.isTraceEnabled() ) {
logger.trace(
"Query execution finished successfully. Response body: "
+ responseBody
+ " Headers: "
+ getNonSensitiveHeadersAsString(responseHeaders)
+ ".");
}
} else {
handleHttpError(configContext, statusCode, responseBody, responseHeaders);
}
}
private void handleHttpError(
@Nonnull final ErpConfigContext configContext,
final int statusCode,
@Nullable final String responseBody,
@Nonnull final List responseHeaders )
throws QueryExecutionException
{
switch( statusCode ) {
case HttpStatus.SC_UNAUTHORIZED:
handleUnauthorized(responseBody, responseHeaders);
return;
case HttpStatus.SC_FORBIDDEN:
handleForbidden(responseBody, responseHeaders);
return;
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
handleInternalServerError(responseBody, responseHeaders);
return;
case HttpStatus.SC_SERVICE_UNAVAILABLE:
handleServiceUnavailableError(configContext, responseBody, responseHeaders);
return;
case HttpStatus.SC_BAD_GATEWAY:
handleBadGateway(responseBody, responseHeaders);
return;
default: {
final String message =
"Query execution failed with status code "
+ statusCode
+ ". Response body: "
+ responseBody
+ " Headers: "
+ getNonSensitiveHeadersAsString(responseHeaders)
+ ".";
throw new QueryExecutionException(message);
}
}
}
private void handleUnauthorized( @Nullable final String responseBody, @Nonnull final List responseHeaders )
throws LogonErrorException
{
final String message =
HttpStatus.SC_UNAUTHORIZED
+ " Unauthorized. The connection attempt was refused. Response body: "
+ responseBody
+ " Headers: "
+ getNonSensitiveHeadersAsString(responseHeaders)
+ ".";
throw new LogonErrorException(message);
}
@Nullable
private String getMissingAuthorization( @Nonnull final List responseHeaders )
{
for( final Header header : responseHeaders ) {
if( header.getName().equals("failed-authorization-object") ) {
return header.getValue();
}
}
return null;
}
private void handleForbidden( @Nullable final String responseBody, @Nonnull final List responseHeaders )
throws AccessDeniedException
{
final String prefix = HttpStatus.SC_FORBIDDEN + " Forbidden. ";
if( responseBody != null && responseBody.startsWith("CX_FINS_MAP_NO_AUTH_QUERY_EXEC") ) {
@Nullable
final String missingAuthorization = getMissingAuthorization(responseHeaders);
throw AccessDeniedException.raiseMissingAuthorizations(
null,
missingAuthorization != null
? Collections.singleton(new ErpAuthorization(missingAuthorization))
: null);
}
final String message =
prefix
+ "Failed to establish a trusted connection to the ERP. This may be caused by "
+ "a misconfiguration of the SAP Cloud Connector or a misconfiguration "
+ "of the trust certificate. Response body: "
+ responseBody
+ " Headers: "
+ getNonSensitiveHeadersAsString(responseHeaders)
+ ".";
throw new AccessDeniedException(message);
}
private
void
handleInternalServerError( @Nullable final String responseBody, @Nonnull final List responseHeaders )
throws QueryExecutionException
{
final String prefix = HttpStatus.SC_INTERNAL_SERVER_ERROR + " Internal Server Error. ";
if( responseBody != null && responseBody.contains("ICF") && responseBody.contains("HCPMAPBM") ) {
final String message =
prefix
+ "Failed to invoke ICF service. Does the user have authorization HCPMAPBM? "
+ "Response body: "
+ responseBody
+ " Headers: "
+ getNonSensitiveHeadersAsString(responseHeaders)
+ ".";
throw new AccessDeniedException(message);
}
final String message =
prefix
+ "Query execution failed with unexpected error. Response body: "
+ responseBody
+ " Headers: "
+ getNonSensitiveHeadersAsString(responseHeaders)
+ ".";
throw new QueryExecutionException(message);
}
private void handleServiceUnavailableError(
@Nonnull final ErpConfigContext configContext,
@Nullable final String responseBody,
@Nonnull final List responseHeaders )
throws QueryExecutionException
{
if( responseBody != null && responseBody.contains("No tunnels subscribed for tunnelId") ) {
final String message =
HttpStatus.SC_SERVICE_UNAVAILABLE
+ " Service Unavailable. Failed to connect to ERP system. "
+ "Please check the configuration of destination '"
+ configContext.getDestinationName()
+ "'. In an on-premise setup, ensure that the cloud connector is connected.";
throw new CloudConnectorException(HttpStatus.SC_SERVICE_UNAVAILABLE, message);
} else {
handleInternalServerError(responseBody, responseHeaders);
}
}
private void handleBadGateway( @Nullable final String responseBody, @Nonnull final List responseHeaders )
throws QueryExecutionException
{
if( responseBody != null && responseBody.contains("Unable to open connection to backend system") ) {
final String message =
HttpStatus.SC_BAD_GATEWAY
+ " Bad Gateway. Cloud connector failed to open connection to backend system. "
+ "Is the internal host configured correctly? Response body: "
+ responseBody
+ " Headers: "
+ getNonSensitiveHeadersAsString(responseHeaders)
+ ".";
throw new CloudConnectorException(HttpStatus.SC_BAD_GATEWAY, message);
} else {
handleInternalServerError(responseBody, responseHeaders);
}
}
/**
* Converts the given headers to a String while omitting sensitive headers to avoid leaking them to logs.
*/
@Nonnull
private String getNonSensitiveHeadersAsString( @Nonnull final List headers )
{
final StringBuilder sb = new StringBuilder();
final Iterator headerIt = headers.iterator();
while( headerIt.hasNext() ) {
final Header header = headerIt.next();
final String name = header.getName();
String value = header.getValue();
if( "set-cookie".equalsIgnoreCase(name) || "authorization".equalsIgnoreCase(name) ) {
value = "(hidden)";
}
sb.append(name).append(": ").append(value).append(headerIt.hasNext() ? ", " : "");
}
return sb.toString();
}
/**
* Serializes the given query, executes it, and the deserializes the response.
*
* @param configContext
* The {@code ErpConfigContext} of this call.
* @param query
* The {@code Query} to be executed.
* @param querySerializer
* The {@code QuerySerializer} to be used to write the query and read the response.
* @return The body of the response received by the given query.
* @throws QuerySerializationException
* If the query could not be serialized
* @throws QueryExecutionException
* If any Exception occured during execution of the query.
* @throws DestinationNotFoundException
* If the Destination cannot be found.
* @throws DestinationAccessException
* If the destination is not of type DestinationType.HTTP or there is an issue while accessing
* destination information.
*/
@Nonnull
public QueryResultT execute(
@Nonnull final ErpConfigContext configContext,
@Nonnull final QueryT query,
@Nonnull final QuerySerializer querySerializer )
throws QuerySerializationException,
QueryExecutionException,
DestinationNotFoundException,
DestinationAccessException
{
assertValidConfigContext(configContext);
measurements.resetMeasurements();
measurements.setBeginTotal(System.nanoTime());
try {
final SerializedQuery serializedQuery = serializeQuery(configContext, query, querySerializer);
final String responseBody = execute(configContext, serializedQuery);
return deserializeQuery(configContext, query, querySerializer, responseBody);
}
finally {
measurements.setEndTotal(System.nanoTime());
}
}
@Nonnull
private SerializedQuery serializeQuery(
@Nonnull final ErpConfigContext configContext,
@Nonnull final QueryT query,
@Nonnull final QuerySerializer querySerializer )
throws QuerySerializationException,
DestinationNotFoundException,
DestinationAccessException
{
final long beginBuildReq = System.nanoTime();
try {
return querySerializer.serialize(query);
}
finally {
final long endBuildReq = System.nanoTime();
measurements.addBuildRequestDuration(Duration.ofNanos(endBuildReq - beginBuildReq));
}
}
@Nonnull
private QueryResultT deserializeQuery(
@Nonnull final ErpConfigContext configContext,
@Nonnull final QueryT query,
@Nonnull final QuerySerializer querySerializer,
@Nonnull final String responseBody )
throws QuerySerializationException,
DestinationNotFoundException,
DestinationAccessException
{
final long beginParseResp = System.nanoTime();
try {
final SerializedQueryResult serializedQueryResult =
new SerializedQueryResult<>(query, responseBody);
return querySerializer.deserialize(serializedQueryResult);
}
finally {
final long endParseResp = System.nanoTime();
measurements.addParseResponseDuration(Duration.ofNanos(endParseResp - beginParseResp));
}
}
@Nonnull
private RequestMethod getRequestMethod( @Nonnull final SerializedQuery serializedQuery )
{
return serializedQuery.getRequestMethod();
}
@Nonnull
protected URI getRequestUri(
@Nonnull final ErpConfigContext configContext,
@Nonnull final Destination destination,
@Nonnull final SerializedQuery serializedQuery )
{
return new ServiceUriBuilder().build(destination.getUri(), serializedQuery.getRequestPath());
}
@Nonnull
private List getRequestHeaders(
final ErpConfigContext configContext,
@Nonnull final SerializedQuery serializedQuery )
{
final List requestHeaders = Lists.newArrayList(serializedQuery.getRequestHeaders());
if( configContext != null ) {
// add HTTP header "sap-client" if a SAP client is defined
final SapClient sapClient = configContext.getSapClient();
if( sapClient != null && !sapClient.isDefault() && !sapClient.isEmpty() ) {
requestHeaders.add(new Header(ErpConfigContext.DEFAULT_SAP_CLIENT_PROPERTY, sapClient.getValue()));
}
// add HTTP header "sap-language" if a language is defined
final Locale locale = configContext.getLocale();
if( locale != null && !Strings.isNullOrEmpty(locale.getLanguage()) ) {
requestHeaders.add(new Header(ErpConfigContext.DEFAULT_LOCALE_PROPERTY, locale.getLanguage()));
}
}
return requestHeaders;
}
private HttpUriRequest newRequest( final RequestMethod requestMethod, final URI requestUri )
{
switch( requestMethod ) {
case GET:
return new HttpGet(requestUri);
case HEAD:
return new HttpHead(requestUri);
case POST:
return new HttpPost(requestUri);
case PUT:
return new HttpPut(requestUri);
case PATCH:
return new HttpPatch(requestUri);
case DELETE:
return new HttpDelete(requestUri);
case OPTIONS:
return new HttpOptions(requestUri);
default:
throw new ShouldNotHappenException("Unsupported request method: " + requestMethod + ".");
}
}
private HttpUriRequest newRequest(
@Nonnull final RequestMethod requestMethod,
@Nonnull final URI requestUri,
@Nonnull final RequestBodyWithHeader bodyWithHeader )
throws QuerySerializationException
{
final long beginBuildRequest = System.nanoTime();
try {
final HttpUriRequest request = newRequest(requestMethod, requestUri);
if( request instanceof HttpEntityEnclosingRequest ) {
((HttpEntityEnclosingRequest) request).setEntity(getBodyAsCompressedEntity(bodyWithHeader.body));
}
request.setHeader(HttpHeaders.USER_AGENT, "ErpEndpointSCP");
request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
for( final Header header : bodyWithHeader.headers ) {
request.setHeader(header.getName(), header.getValue());
}
if( logger.isTraceEnabled() ) {
final Thread currentThread = Thread.currentThread();
logger.trace(
"Successfully prepared HTTP request for query execution (thread: "
+ currentThread
+ ", threat id: "
+ currentThread.getId()
+ ") URI: "
+ requestUri
+ " Body: "
+ bodyWithHeader.body
+ " Headers: "
+ getNonSensitiveHeadersAsString(bodyWithHeader.headers)
+ ".");
}
return request;
}
finally {
measurements.addBuildRequestDuration(Duration.ofNanos(System.nanoTime() - beginBuildRequest));
}
}
private void logReadAccessAttempt( final Query, ?> query, ErpConfigContext context )
{
@Nullable
final String readAccessData = query.getReadAccessData();
if( readAccessData != null ) {
AuditLogger.logDataReadAttempt(
new AuditedDataObject(query.getClass().getSimpleName()),
new AuditedDataSubject(context.getDestinationName(), context.getSapClient().getValue()),
new AccessedAttribute(readAccessData, AccessedAttribute.Operation.READ));
}
}
private String getQueryExecutionFailedMessage( final Query, ?> query )
{
return query.getClass().getSimpleName()
+ " "
+ query.getConstructedByMethod()
+ " failed ["
+ measurements.getMeasurementsString()
+ "]";
}
private static final Duration DEFAULT_LONG_RUNNING_REQUEST_THRESHOLD = Duration.ofMillis(2000);
private void recordExecutionDuration(
final SerializedQuery extends Query, ?>> serializedQuery,
final RequestLine requestLine,
final StatusLine statusLine,
final List responseHeaders,
final String responseBody )
{
final Duration executeRequestDuration = measurements.getExecuteRequestDuration();
final Duration longRunningRequestThreshold = serializedQuery.getQuery().getLongRunningRequestThreshold();
final Duration currLongRunningRequestThreshold =
longRunningRequestThreshold != null ? longRunningRequestThreshold : DEFAULT_LONG_RUNNING_REQUEST_THRESHOLD;
if( executeRequestDuration != null && currLongRunningRequestThreshold.compareTo(executeRequestDuration) < 0 ) {
ErpEndpointMonitor.getInstance().trackLongRunningRequest(
measurements.getExecuteRequestDuration(),
requestLine.toString(),
serializedQuery.getRequestBody(),
statusLine.toString(),
responseHeaders,
responseBody.length());
}
}
/**
* Executes the given {@code serializedQuery} as a {@code HttpUriRequest}, returning the body of the
* {@code HttpResponse} received.
*
* @param configContext
* The {@code ErpConfigContext} of this call.
* @param serializedQuery
* The {@code SerializedQuery} to execute.
*
* @return The body of the response received by the given query.
*
* @throws QuerySerializationException
* If the query could not be serialized.
* @throws QueryExecutionException
* If any Exception occured during execution of the query.
* @throws DestinationNotFoundException
* If the Destination cannot be found.
* @throws DestinationAccessException
* If the destination is not of type DestinationType.HTTP or there is an issue while accessing
* destination information.
*/
@Nonnull
public
String
execute( @Nonnull final ErpConfigContext configContext, @Nonnull final SerializedQuery serializedQuery )
throws QuerySerializationException,
QueryExecutionException,
DestinationNotFoundException,
DestinationAccessException
{
final QueryT query = serializedQuery.getQuery();
ErpEndpointMonitor.getInstance().incrementErpQueryCount(query);
final Destination destination = DestinationAccessor.getDestination(configContext.getDestinationName());
final HttpClient httpClient = HttpClientAccessor.getHttpClient(destination);
final RequestMethod requestMethod = getRequestMethod(serializedQuery);
final URI requestUri = getRequestUri(configContext, destination, serializedQuery);
// resolve request body and header for potentially signed request body
final RequestBodyWithHeader bodyWithHeader = getRequestBodyWithHeader(configContext, serializedQuery);
final HttpUriRequest request = newRequest(requestMethod, requestUri, bodyWithHeader);
HttpResponse response;
final List responseHeaders = new ArrayList<>();
final String responseBody;
final long beginExecute = System.nanoTime();
try {
if( logger.isDebugEnabled() ) {
logger.debug(
"Executing "
+ query.getClass().getSimpleName()
+ " constructed by: "
+ query.getConstructedByMethod()
+ ".");
}
logReadAccessAttempt(query, configContext);
response = httpClient.execute(request);
for( final org.apache.http.Header header : response.getAllHeaders() ) {
responseHeaders.add(new Header(header.getName(), header.getValue()));
}
responseBody = HttpEntityUtil.getResponseBody(response);
}
catch( final QuerySerializationException e ) {
if( logger.isDebugEnabled() ) {
logger.debug(getQueryExecutionFailedMessage(query), e);
}
throw e;
}
catch( final Exception e ) {
final String message = getQueryExecutionFailedMessage(query);
throw new QueryExecutionException(message, e);
}
finally {
measurements.addExecuteRequestDuration(Duration.ofNanos(System.nanoTime() - beginExecute));
}
if( responseBody == null ) {
throw new QueryExecutionException("Failed to execute query: no body returned in response.");
}
recordExecutionDuration(
serializedQuery,
request.getRequestLine(),
response.getStatusLine(),
responseHeaders,
responseBody);
handleHttpStatus(configContext, response.getStatusLine().getStatusCode(), responseBody, responseHeaders);
return responseBody;
}
/**
* Returns a wrapper object which encapsulates the HTTP request body and headers. This method can be overridden to
* manipulate the request before submitting, e.g. signing queries, adding timestamps.
*
* @param configContext
* The {@code ErpConfigContext} of this call.
* @param query
* The {@code Query} to be executed.
*/
@Nonnull
protected
RequestBodyWithHeader
getRequestBodyWithHeader( final ErpConfigContext configContext, @Nonnull final SerializedQuery query )
{
return new RequestBodyWithHeader(getRequestHeaders(configContext, query), query.getRequestBody());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy