/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-2597
package org.teiid.resource.adapter.ws;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.jaxws.DispatchImpl;
import org.apache.cxf.message.Message;
import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.auth.NTLMSchemeFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.ietf.jgss.GSSCredential;
import org.teiid.core.util.ArgCheck;
import org.teiid.core.util.Base64;
import org.teiid.core.util.StringUtil;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.resource.spi.BasicConnection;
import org.teiid.resource.spi.ConnectionContext;
import org.teiid.translator.WSConnection;

import javax.activation.DataSource;
import javax.security.auth.Subject;
import javax.xml.namespace.QName;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.*;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.http.HTTPException;
import javax.xml.ws.soap.SOAPFaultException;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Future;

/**
 * WebService connection implementation.
 * <p>
 * TODO: set a handler chain
 */
public class WSConnectionImpl extends BasicConnection implements WSConnection {
	private static final String CONNECTION_TIMEOUT = "javax.xml.ws.client.connectionTimeout"; //$NON-NLS-1$
	private static final String RECEIVE_TIMEOUT = "javax.xml.ws.client.receiveTimeout"; //$NON-NLS-1$

	private boolean failOnHttpError = true;
	
	private static final class HttpDataSource implements DataSource {
		private final URL url;
		private final HttpResponse httpResponse;

		private final boolean failOnHttpError;
		private final boolean decompressCompressedFiles;

		private HttpDataSource(URL url, HttpResponse httpResponse, boolean failOnHttpError, Boolean decompressCompressedFiles) {
			this.url = url;
			this.failOnHttpError = failOnHttpError;
			this.httpResponse = httpResponse;
            this.decompressCompressedFiles = Objects.requireNonNullElse(decompressCompressedFiles, false);
		}
		
		@Override
		public OutputStream getOutputStream() {
			throw new UnsupportedOperationException();
		}

		@Override
		public String getName() {
			return url.getPath();
		}

		@Override
		public InputStream getInputStream() throws IOException {
				if (httpResponse.getStatusLine().getStatusCode() >= 400) {
				HttpUtils.logException(httpResponse.getStatusLine().getStatusCode(),
						httpResponse.getStatusLine().getReasonPhrase(), this.failOnHttpError);
				}
			StringBuilder errMsg = new StringBuilder();
			try  {
				final InputStream content = httpResponse.getEntity() != null
						? httpResponse.getEntity().getContent()
						: InputStream.nullInputStream();
				return HttpUtils.getInputStream(this.failOnHttpError, this.decompressCompressedFiles,
						httpResponse.getStatusLine().getStatusCode(), httpResponse.getStatusLine().getReasonPhrase(),
						content, content, errMsg);
			} catch (IOException e) {
				IOException e1 = new IOException(e.getMessage() + errMsg, e);
				LogManager.logError(LogConstants.CTX_CONNECTOR, e1, "Error executing http request");
				if (failOnHttpError) {
					throw e1;
				}
				return HttpUtils.handleError(null, errMsg);
			}
		}


		@Override
		public String getContentType() {
			return httpResponse.getEntity() != null && httpResponse.getEntity().getContentType() != null ?
					httpResponse.getEntity().getContentType().getValue() : null;
			}

		}

	private HttpClient createHttpClient() {
		HttpClient httpClient;
		if (mcf.getAsSecurityType() == WSManagedConnectionFactory.SecurityType.NTLM) {
			CredentialsProvider credsProvider = new BasicCredentialsProvider();
			credsProvider.setCredentials(AuthScope.ANY, new NTCredentials(mcf.getAuthUserName(), mcf.getAuthPassword(),
					mcf.getAuthHost(), mcf.getAuthDomain()));
			Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
					.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
					.build();
			RequestConfig config = RequestConfig.custom()
					.setTargetPreferredAuthSchemes(List.of(AuthSchemes.NTLM))
					.build();
			httpClient = HttpClientBuilder.create()
					.setDefaultCredentialsProvider(credsProvider)
					.setDefaultAuthSchemeRegistry(authSchemeRegistry)
					.setDefaultCookieStore(new BasicCookieStore())
					.setDefaultRequestConfig(config)
					.build();
		} else
			httpClient = HttpClients.createSystem();
		return httpClient;
	}
	
	/**
	 * Workaround dispatch, since neither JBossNative nor CXF 2.2.2 implement
	 * this functionality.
	 */
	private final class BinaryDispatch implements Dispatch<DataSource> {

		HashMap<String, Object> requestContext = new HashMap<>();
		HashMap<String, Object> responseContext = new HashMap<>();
		
		private final String endpoint;
		
		private final boolean failOnHttpError;
		private final Boolean decompressCompressedFiles;

		public BinaryDispatch(String endpoint, boolean failOnHttpError, Boolean decompressCompressedFiles) {
			this.endpoint = endpoint;
			this.failOnHttpError = failOnHttpError;
            this.decompressCompressedFiles = Objects.requireNonNullElse(decompressCompressedFiles, false);
			}
		
				@Override
		public DataSource invoke(DataSource msg) {
			try {
				String method = requestContext.get(MessageContext.HTTP_REQUEST_METHOD).toString().toUpperCase();
					final URL url = new URL(endpoint);
				HttpRequestBase request = createHttpMethod(method, endpoint, msg);
				setupRequest(request);
				HttpClient client = createHttpClient();
				final HttpResponse response = client.execute(request);
				return handleResponse(url, response);
			} catch (IOException e) {
				throw new WebServiceException(e);
			}
		}

		private HttpRequestBase createHttpMethod(String method, String endpoint, DataSource msg) throws IOException {
			return msg != null
					? new HttpMethodWithEntity(method, endpoint, new InputStreamEntity(msg.getInputStream()))
					: new HttpMethod(method, endpoint);
				}

		private void setupRequest(HttpRequestBase request) throws IOException {
			//noinspection unchecked
			Map<String, List<String>> header = (Map<String, List<String>>) requestContext.get(MessageContext.HTTP_REQUEST_HEADERS);
			for (Map.Entry<String, List<String>> entry : header.entrySet()) {
				String value = StringUtil.join(entry.getValue(), ","); //$NON-NLS-1$
				request.setHeader(entry.getKey(), value);
			}
			if (header.get("Authorization") == null) {
				String authorizationProperty = getAuthorizationProperty();
				if (authorizationProperty != null)
					request.setHeader("Authorization", authorizationProperty);
			}
			final RequestConfig.Builder requestConfig = RequestConfig.custom();
			Long timeout = (Long) requestContext.get(RECEIVE_TIMEOUT);
			if (timeout != null)
				requestConfig.setSocketTimeout(timeout.intValue());
			timeout = (Long) requestContext.get(CONNECTION_TIMEOUT);
			if (timeout != null)
				requestConfig.setConnectTimeout(timeout.intValue());
			request.setConfig(requestConfig.build());
				}

		private String getAuthorizationProperty() {
			String username = (String) requestContext.get(Dispatch.USERNAME_PROPERTY);
			String password = (String) requestContext.get(Dispatch.PASSWORD_PROPERTY);

			String accessToken = (String) requestContext.get(ACCESS_TOKEN);
			String refreshToken = (String) requestContext.get(REFRESH_TOKEN);

			if (username != null) {
				return "Basic " + Base64.encodeBytes((username + ':' + password).getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
			} else if (accessToken != null && refreshToken == null) {
				return "Bearer " + accessToken;
			} else {
				try {
					accessToken = getValidAccessToken();
					if (accessToken != null)
						return "Bearer " + accessToken;
				} catch (Exception e) {
					throw new WebServiceException(e);
				}
			}
			return null;
		}

		public Map<String, List<String>> getHeaders(HttpResponse response) {
			Map<String, List<String>> responseHeaders = new HashMap<>();
			HeaderIterator iterator = response.headerIterator();
			while (iterator.hasNext()) {
				Header responseHeader = iterator.nextHeader();
				responseHeaders.put(responseHeader.getName(), Collections.singletonList(responseHeader.getValue()));
			}
			return responseHeaders;
		}

		private DataSource handleResponse(URL url, HttpResponse response) {
				boolean failed;
			int responseCode = response.getStatusLine().getStatusCode();
			failed = !failOnHttpError && responseCode >= 400;
			responseContext.put(MessageContext.HTTP_RESPONSE_HEADERS, getHeaders(response));
				responseContext.put(INVOCATION_FAILED, failed);
				responseContext.put(RESPONSE_CODE, responseCode);
			return new HttpDataSource(url, response, failOnHttpError, decompressCompressedFiles);
			}

		private String getValidAccessToken() {
			String clientId = (String) requestContext.get(CLIENT_ID);
			String clientSecret = (String) requestContext.get(CLIENT_SECRET);
			String authCode = (String) requestContext.get(AUTH_CODE);
			if (authCode != null) {
				try {
					String redirectUri = (String) requestContext.get(REDIRECT_URI);
					String dataSourceName = (String) requestContext.get(DATASOURCE_NAME);
					return (String) DVCaller.callWithExc(129, clientId, clientSecret, authCode, redirectUri, dataSourceName);
				} catch (Throwable e) {
					LogManager.logError(LogConstants.CTX_CONNECTOR, e, "Error getting OAuth2 access token");
					return null;
				}
			} else {
				try {
					String refreshToken = (String) requestContext.get(REFRESH_TOKEN);
					String scope = (String) requestContext.get(SCOPE);
					String accessTokenEndPoint = (String) requestContext.get(ENDPOINT_TO_GET_VALID_ACCESS_TOKEN);
					String dataSourceName = (String) requestContext.get(DATASOURCE_NAME);
					if (((refreshToken == null) && (scope == null)) || (accessTokenEndPoint == null || dataSourceName == null))
						return null;
					return (String) DVCaller.callWithExc(149, clientId, clientSecret, refreshToken, scope, accessTokenEndPoint, dataSourceName);
				} catch (Throwable e) {
					LogManager.logError(LogConstants.CTX_CONNECTOR, e, "Error getting OAuth2 access token (WS workflow)");
					return null;
				}
			}
		}
		
		@Override
		public Response<DataSource> invokeAsync(DataSource msg) {
			throw new UnsupportedOperationException();
		}

		@Override
		public Future<?> invokeAsync(DataSource msg, AsyncHandler<DataSource> handler) {
			throw new UnsupportedOperationException();
		}

		@Override
		public void invokeOneWay(DataSource msg) {
			throw new UnsupportedOperationException();
		}

		@Override
		public Binding getBinding() {
			throw new UnsupportedOperationException();
		}

		@Override
		public EndpointReference getEndpointReference() {
			throw new UnsupportedOperationException();
		}

		@Override
		public <T extends EndpointReference> T getEndpointReference(
				Class<T> clazz) {
			throw new UnsupportedOperationException();
		}

		@Override
		public Map<String, Object> getRequestContext() {
			return requestContext;
		}

		@Override
		public Map<String, Object> getResponseContext() {
			return responseContext;
		}
	}

	private static final class XMLDispatch<T> implements Dispatch<T> {
		private final Dispatch<T> dispatch;
		private final boolean failOnHttpError;

		public XMLDispatch(Dispatch<T> dispatch, boolean failOnHttpError) {
			this.dispatch = dispatch;
			this.failOnHttpError = failOnHttpError;
		}

		@Override
		public T invoke(T msg) {
			boolean failed = false;
			Integer responseCode = null;
			try {
				return dispatch.invoke(msg);
			} catch (RuntimeException e) {
				failed = true;
				boolean rawData = false;
				StringBuilder errorDetail = new StringBuilder();
				if (e instanceof SOAPFaultException) {
					SOAPFaultException soapError = (SOAPFaultException)e;
					if (soapError.getFault() != null) {
						StringWriter buf = new StringWriter();
						try {
							// 1. oracle xml transformer can't parse SOAPFault node (it's impossible to debug the library because it doesn't have source code at all)
							// 2. had to hardcode usage of xalan because JBoss uses oracle xml transformer which is configured via JBoss system properties
							//    the system properties have the highest priority comparing with Services API (The Services API will look for a
							//    classname in the file META-INF/services/javax.xml.transform.TransformerFactory in jars available to the runtime)
							Transformer xForm = TransformerFactory.newInstance("org.apache.xalan.processor.TransformerFactoryImpl", this.getClass().getClassLoader()).newTransformer();
							xForm.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
							xForm.setOutputProperty(OutputKeys.INDENT, "yes");
							xForm.transform(new DOMSource(soapError.getFault()), new StreamResult(buf));
							errorDetail.append(buf);
						} catch (TransformerException e1) {
							LogManager.logError(LogConstants.CTX_CONNECTOR, e1, "Error parsing soap fault's details");
						}
					}
				} else if (e instanceof HTTPException) {
					rawData = true;
					HTTPException httpError = (HTTPException)e;
					responseCode = httpError.getStatusCode();
					errorDetail.append(" [*HTTP Response: ");
					errorDetail.append(responseCode);
					errorDetail.append("*]");
					errorDetail.append("** http error message **\r\n");
					if (httpError.getCause() != null) {
						Throwable cause = httpError.getCause();
						if (cause.getMessage() != null) {
							errorDetail.append(cause.getMessage());
						}
					} else {
						for (StackTraceElement element : httpError.getStackTrace())
							errorDetail.append(element.toString()).append("\r\n");
					}
				}
				RuntimeException e1 = errorDetail.length() > 0 ?
						new RuntimeException((e.getMessage() != null ? e.getMessage() + "\n " : "") + "Details:\n" + errorDetail) : e;
				LogManager.logError(LogConstants.CTX_CONNECTOR, e1, "Error executing http request");
				if (failOnHttpError) {
					throw e1;
				} else
					//noinspection unchecked
					return handleError((Class<T>)msg.getClass(), errorDetail, rawData);
			} finally {
				if (dispatch.getResponseContext().containsKey(Message.RESPONSE_CODE)) {
					responseCode = (Integer)dispatch.getResponseContext().get(Message.RESPONSE_CODE);
				}
				if (!failed && responseCode != null && responseCode >= 400)
					failed = true;

				dispatch.getResponseContext().put(INVOCATION_FAILED, failed);
				dispatch.getResponseContext().put(RESPONSE_CODE, responseCode);
			}
		}

		@Override
		public Response<T> invokeAsync(T msg) {
			return dispatch.invokeAsync(msg);
		}

		@Override
		public Future<?> invokeAsync(T msg, AsyncHandler<T> handler) {
			return dispatch.invokeAsync(msg, handler);
		}

		@Override
		public void invokeOneWay(T msg) {
			dispatch.invokeOneWay(msg);
		}

		@Override
		public Map<String, Object> getRequestContext() {
			return dispatch.getRequestContext();
		}

		@Override
		public Map<String, Object> getResponseContext() {
			return dispatch.getResponseContext();
		}

		@Override
		public Binding getBinding() {
			return dispatch.getBinding();
		}

		@Override
		public EndpointReference getEndpointReference() {
			return dispatch.getEndpointReference();
		}

		@Override
		public <R extends EndpointReference> R getEndpointReference(Class<R> clazz) {
			return dispatch.getEndpointReference(clazz);
		}

		private T handleError(Class<T> clazz, StringBuilder errorDetail, boolean rawData) {
			if (clazz == StreamSource.class) {
				if (errorDetail != null && errorDetail.length() > 0) {
					if (rawData) {
						errorDetail.insert(0, "<errorDetail><![CDATA[").append("]]></errorDetail>");
					}
					//noinspection unchecked
					return (T)new StreamSource(new ByteArrayInputStream(errorDetail.toString().getBytes()));
				}
			}
			return null;
		}
	}

	static final String DEFAULT_NAMESPACE_URI = "http://teiid.org"; //$NON-NLS-1$
	static final String DEFAULT_LOCAL_NAME = "teiid"; //$NON-NLS-1$

	private static final QName svcQname = new QName(DEFAULT_NAMESPACE_URI, DEFAULT_LOCAL_NAME);
	private final WSManagedConnectionFactory mcf;
	
	public WSConnectionImpl(WSManagedConnectionFactory mcf) {
		this.mcf = mcf;
	}

	public <T> Dispatch<T> createDispatch(String binding, String endpoint, Class<T> type, Mode mode, boolean failOnHttpError) {
		this.failOnHttpError = failOnHttpError;
		return createDispatch(binding, endpoint, type, mode);
	}

	public <T> Dispatch<T> createDispatch(String binding, String endpoint, Class<T> type, Mode mode) {
		ArgCheck.isNotNull(binding);
		if (endpoint != null) {
			try {
				new URL(endpoint);
				//valid url, just use the endpoint
			} catch (MalformedURLException e) {
				//otherwise it should be a relative value
				//but we should still preserve the base path and query string
				String defaultEndpoint = getEndPoint();
				String defaultQueryString = null;
				String defaultFragment = null;
				if (defaultEndpoint == null) {
					throw new WebServiceException(WSManagedConnectionFactory.UTIL.getString("null_default_endpoint")); //$NON-NLS-1$
				}
				String[] parts = defaultEndpoint.split("\\?", 2); //$NON-NLS-1$
				defaultEndpoint = parts[0];
				if (parts.length > 1) {
					defaultQueryString = parts[1];
					parts = defaultQueryString.split("#"); //$NON-NLS-1$
					defaultQueryString = parts[0];
					if (parts.length > 1) {
						defaultFragment = parts[1];
					}
				}
				int slashes = 0;
				if (defaultEndpoint.endsWith("/")) //$NON-NLS-1$
					slashes++;
				if (endpoint.startsWith("/")) //$NON-NLS-1$
					slashes++;
				if (endpoint.startsWith("?")) //$NON-NLS-1$
					slashes = 1;
				switch (slashes) {
					case 0:
						endpoint = defaultEndpoint + "/" + endpoint; //$NON-NLS-1$
						break;
					case 2:
						endpoint = endpoint.substring(1);
					case 1:
						endpoint = defaultEndpoint + endpoint;
						break;
				}
				if (defaultQueryString != null && !defaultQueryString.trim().isEmpty()) {
					endpoint = WSConnection.Util.appendQueryString(endpoint, defaultQueryString);
				}
				if (defaultFragment != null && endpoint.indexOf('#') < 0) {
					endpoint = endpoint + '#' + defaultFragment;
				}
			}
		} else {
			endpoint = getEndPoint();
			if (endpoint == null) {
				throw new WebServiceException(WSManagedConnectionFactory.UTIL.getString("null_endpoint")); //$NON-NLS-1$
			}
		}
		Dispatch<T> dispatch;
		if (HTTPBinding.HTTP_BINDING.equals(binding) && type == DataSource.class) {
			//noinspection unchecked
			dispatch = (Dispatch<T>) new BinaryDispatch(endpoint, failOnHttpError, mcf.getDecompressCompressedFiles());
		} else {
			//TODO: cache service/port/dispatch instances?
			Bus bus = BusFactory.getThreadDefaultBus();
			BusFactory.setThreadDefaultBus(mcf.getBus());
			Service svc;
			try {
				svc = Service.create(svcQname);
			} finally {
				BusFactory.setThreadDefaultBus(bus);
			}
			if (LogManager.isMessageToBeRecorded(LogConstants.CTX_WS, MessageLevel.DETAIL)) {
				LogManager.logDetail(LogConstants.CTX_WS, "Creating a dispatch with endpoint", endpoint); //$NON-NLS-1$
			}
			svc.addPort(mcf.getPortQName(), binding, endpoint);
			
			dispatch = new XMLDispatch<>(svc.createDispatch(mcf.getPortQName(), type, mode), failOnHttpError);
			
			if (mcf.getAsSecurityType() == WSManagedConnectionFactory.SecurityType.WSSECURITY
					&& mcf.getOutInterceptors() != null) {
				//noinspection DataFlowIssue
				Client client = ((DispatchImpl<?>)dispatch).getClient();
				Endpoint ep = client.getEndpoint();
				for (Interceptor<?> i : mcf.getOutInterceptors()) {
					ep.getOutInterceptors().add(i);
				}
			}
		}
		
		if (mcf.getAsSecurityType() == WSManagedConnectionFactory.SecurityType.HTTPBASIC) {
			dispatch.getRequestContext().put(Dispatch.USERNAME_PROPERTY, mcf.getAuthUserName());
			dispatch.getRequestContext().put(Dispatch.PASSWORD_PROPERTY, mcf.getAuthPassword());
		}
		else if (this.mcf.getAsSecurityType() == WSManagedConnectionFactory.SecurityType.KERBEROS) {
			boolean credentialFound = false;
			Subject subject = ConnectionContext.getSubject();
			if (subject != null) {
				GSSCredential credential = ConnectionContext.getSecurityCredential(subject, GSSCredential.class);
				if (credential != null) {
					dispatch.getRequestContext().put(GSSCredential.class.getName(), credential);
					credentialFound = true;
				}
			}
			if (!credentialFound) {
				throw new WebServiceException(WSManagedConnectionFactory.UTIL.getString("no_gss_credential")); //$NON-NLS-1$
			}
		}
		
		if (mcf.getAsSecurityType() == WSManagedConnectionFactory.SecurityType.OAUTH2) {
			String translatorName = mcf.getTranslatorName();
			if (translatorName == null)
				throw new WebServiceException(ConnectorWsPlugin.Util.gs(ConnectorWsPlugin.Event.DVCORE94057));
			dispatch.getRequestContext().put(ACCESS_TOKEN, mcf.getAccessToken());
			dispatch.getRequestContext().put(REFRESH_TOKEN, mcf.getRefreshToken());
			dispatch.getRequestContext().put(SCOPE, mcf.getScope());
			dispatch.getRequestContext().put(ENDPOINT_TO_GET_VALID_ACCESS_TOKEN, mcf.getAccessTokenEndpoint());
			String dataSourceName = mcf.getDataSourceName();
			dispatch.getRequestContext().put(DATASOURCE_NAME, dataSourceName);
			if (mcf.getAuthCode() != null) {
				dispatch.getRequestContext().put(AUTH_CODE, mcf.getAuthCode());
				dispatch.getRequestContext().put(REDIRECT_URI, mcf.getRedirectUri());
			}
			try {
				DVCaller.callWithExc(87, dispatch.getRequestContext(), dataSourceName, translatorName, mcf.getClientId(), mcf.getClientSecret());
			} catch (Throwable e) {
				throw new WebServiceException(e);
			}
		}
		
		if (this.mcf.getRequestTimeout() != null){
			dispatch.getRequestContext().put(RECEIVE_TIMEOUT, this.mcf.getRequestTimeout());
		}
		if (this.mcf.getConnectTimeout() != null){
			dispatch.getRequestContext().put(CONNECTION_TIMEOUT, this.mcf.getConnectTimeout());
		}

		if (HTTPBinding.HTTP_BINDING.equals(binding)) {
			//noinspection unchecked
	        Map<String, List<String>> httpHeaders = (Map<String, List<String>>)dispatch.getRequestContext().get(MessageContext.HTTP_REQUEST_HEADERS);
	        if(httpHeaders == null) {
	        	httpHeaders = new HashMap<>();
	        }
	        httpHeaders.put("Content-Type", Collections.singletonList("text/xml; charset=utf-8"));//$NON-NLS-1$ //$NON-NLS-2$
	        httpHeaders.put("User-Agent", Collections.singletonList("DataVirtuality Server"));//$NON-NLS-1$ //$NON-NLS-2$
			String cloudAgentWsHost = mcf.getCloudAgentWsHost();
			if (cloudAgentWsHost != null && !cloudAgentWsHost.isBlank()) {
				httpHeaders.put("HOST", Collections.singletonList(cloudAgentWsHost));
			}
	        dispatch.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, httpHeaders);
		}
		return dispatch;
	}

	private String getEndPoint() {
		String value = mcf.getEndPoint();
		if (value != null && value.startsWith("\"") && value.endsWith("\"")) {
			value = value.substring(1, value.length() - 1);
		}
		return value;
	}

	@Override
	public void close() {
	}
}
