com.sap.cloud.sdk.s4hana.connectivity.soap.SoapQuery Maven / Gradle / Ivy
Show all versions of soap Show documentation
/*
* Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.cloud.sdk.s4hana.connectivity.soap;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Stub;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.databinding.utils.ConverterUtil;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.transport.TransportSender;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
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.ProxyConfiguration;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.s4hana.connectivity.ErpConfigContext;
import io.vavr.CheckedFunction1;
/**
* Class representing a query calling a SOAP service in an ERP system.
*
* This class instantiates a service class which extends {@link Stub} from the Axis2 framework and prepares the Axis2
* configuration context according to the provided {@link ErpConfigContext}.
*
* Use the static method {@code registerCustomConverter} to register your own custom converter class which Axis2 uses
* for converting values from the XSD types of the SOAP envelope to Java types.
*
* By default, the provided class {@link SoapCustomConverter} is registered at application startup.
*
* @param
* Subtype of {@link Stub} representing your SOAP service.
*/
public class SoapQuery
{
private static final Logger logger = CloudLoggerFactory.getLogger(SoapQuery.class);
private static final int DEFAULT_HTTP_PORT = 80;
private static final int DEFAULT_HTTPS_PORT = 443;
/**
* Use this method to register your own custom converter class which Axis2 uses for converting values from the SOAP
* envelope following XSD types to Java types.
*
* Your converter class must be a subtype of {@link ConverterUtil}.
*
* @param customConverter
* Your converter class
* @throws SoapException
* Thrown in case the custom converter class could not be registered within the Axis2 framework.
*/
public static void registerCustomConverter( @Nonnull final Class extends ConverterUtil> customConverter )
throws SoapException
{
try {
if( logger.isDebugEnabled() ) {
logger.debug("Registering Axis2 custom converter class " + customConverter.getSimpleName() + ".");
}
final java.lang.reflect.Field isCustomClassPresentField =
org.apache.axis2.databinding.utils.ConverterUtil.class.getDeclaredField("isCustomClassPresent");
isCustomClassPresentField.setAccessible(true);
isCustomClassPresentField.setBoolean(null, true);
isCustomClassPresentField.setAccessible(false);
final java.lang.reflect.Field customClassField =
org.apache.axis2.databinding.utils.ConverterUtil.class.getDeclaredField("customClass");
customClassField.setAccessible(true);
customClassField.set(null, customConverter);
customClassField.setAccessible(false);
}
catch( final NoSuchFieldException | IllegalAccessException e ) {
throw new SoapException(
"Error while registering Custom Converter class "
+ customConverter.getSimpleName()
+ " in the Axis2 library.",
e);
}
}
private static final int MAX_TOTAL_CONNECTIONS = 200;
private static final int MAX_CONNECTIONS_PER_ROUTE = 100;
/**
* Returns the instance of the class {@code ServiceT} which was created by this {@code SoapQuery}.
*/
private final ServiceT service;
/**
* Takes the class type of the SOAP service type {@code ServiceT} as {@code serviceClass} and an
* {@link ErpConfigContext}, creates and prepares the Axis2 configuration context and instantiates the class
* {@code ServiceT}.
*
* @param serviceClass
* Class type of {@code ServiceT}
* @param configContext
* An instance of {@code {@link ErpConfigContext}}
* @throws SoapException
* Thrown in case the Axis2 configuration context could not be prepared or the service instance could
* not be instantiated.
*/
public SoapQuery( @Nonnull final Class serviceClass, @Nonnull final ErpConfigContext configContext )
throws SoapException
{
service = instantiateServiceClass(serviceClass, getServiceConfigurationContext());
prepareSoapCall(service, configContext);
}
/**
* Takes the class type of the SOAP service type {@code ServiceT} as {@code serviceClass}, instantiates a default
* {@link ErpConfigContext}, creates and prepares the Axis2 configuration context and instantiates the class
* {@code ServiceT}.
*
* @param serviceClass
* Class type of {@code ServiceT}
* @throws SoapException
* Thrown in case the Axis2 configuration context could not be prepared or the service instance could
* not be instantiated.
*/
public SoapQuery( @Nonnull final Class serviceClass ) throws SoapException
{
final ConfigurationContext configurationContext = getServiceConfigurationContext();
service = instantiateServiceClass(serviceClass, configurationContext);
prepareSoapCall(service, new ErpConfigContext());
}
@Nonnull
private ServiceT instantiateServiceClass(
@Nonnull final Class serviceClass,
@Nonnull final ConfigurationContext configurationContext )
throws SoapException
{
try {
return serviceClass.getConstructor(ConfigurationContext.class).newInstance(configurationContext);
}
catch( final
InstantiationException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException e ) {
throw new SoapException(
"Error during constructor invocation of class " + serviceClass.getSimpleName() + ".",
e);
}
}
@Nonnull
private ConfigurationContext getServiceConfigurationContext()
throws SoapException
{
try {
final ConfigurationContext configurationContext =
ConfigurationContextFactory.createDefaultConfigurationContext();
specifyUsageOfHttpClient4(configurationContext);
return configurationContext;
}
catch( final Exception e ) {
throw new SoapException("Error while preparing Axis2 configuration context: " + e.getMessage() + ".", e);
}
}
private void specifyUsageOfHttpClient4( @Nonnull final ConfigurationContext configurationContext )
throws SoapException
{
final AxisConfiguration axisConfiguration = configurationContext.getAxisConfiguration();
axisConfiguration.getTransportsOut().get("https").setSender(new DefaultHttpClientTransportSender());
axisConfiguration.getTransportsOut().get("http").setSender(new DefaultHttpClientTransportSender());
final HashMap transportsOut = axisConfiguration.getTransportsOut();
for( final TransportOutDescription transportOut : transportsOut.values() ) {
final TransportSender sender = transportOut.getSender();
if( sender != null ) {
try {
sender.init(configurationContext, transportOut);
}
catch( final AxisFault e ) {
throw new SoapException("Error while initializing Axis2 library.", e);
}
}
}
if( logger.isDebugEnabled() ) {
logger.debug(DefaultHttpClientTransportSender.class.getSimpleName() + " set in Axis2 configuration.");
}
}
private void prepareSoapCall( @Nonnull final ServiceT service, @Nonnull final ErpConfigContext configContext )
throws SoapException
{
final Destination destination = DestinationAccessor.getDestination(configContext.getDestinationName());
try {
setTargetUriOfSoapCall(service, configContext);
setHeadersOfSoapCall(service, destination);
setProxyOfSoapCall(service, destination);
setTrustAllOfSoapCall(service, destination);
}
catch( final
KeyManagementException
| NoSuchAlgorithmException
| UnrecoverableKeyException
| KeyStoreException e ) {
throw new SoapException(e);
}
}
@SuppressWarnings( "deprecation" )
private void setTrustAllOfSoapCall( @Nonnull final ServiceT service, @Nonnull final Destination destination )
throws UnrecoverableKeyException,
NoSuchAlgorithmException,
KeyStoreException,
KeyManagementException
{
if( destination.isTrustingAllCertificates() ) {
final TrustAllSslSocketFactory socketFactory = new TrustAllSslSocketFactory(destination);
final org.apache.http.conn.ClientConnectionManager connectionManager =
buildConnectionManager(socketFactory);
service._getServiceClient().getOptions().setProperty(
HTTPConstants.MULTITHREAD_HTTP_CONNECTION_MANAGER,
connectionManager);
}
}
@SuppressWarnings( "deprecation" )
@Nonnull
private org.apache.http.conn.ClientConnectionManager buildConnectionManager(
@Nonnull final TrustAllSslSocketFactory socketFactory )
{
final org.apache.http.conn.scheme.SchemeRegistry schemeRegistry =
new org.apache.http.conn.scheme.SchemeRegistry();
schemeRegistry.register(
new org.apache.http.conn.scheme.Scheme(
"http",
DEFAULT_HTTP_PORT,
org.apache.http.conn.scheme.PlainSocketFactory.getSocketFactory()));
schemeRegistry.register(new org.apache.http.conn.scheme.Scheme("https", DEFAULT_HTTPS_PORT, socketFactory));
final org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager connectionManager =
new org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager(schemeRegistry);
connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
connectionManager.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
return connectionManager;
}
@SuppressWarnings( "deprecation" )
private static class TrustAllSslSocketFactory extends org.apache.http.conn.ssl.SSLSocketFactory
{
TrustAllSslSocketFactory( final Destination destination )
throws NoSuchAlgorithmException,
KeyManagementException,
KeyStoreException,
UnrecoverableKeyException
{
super(new TrustAllStrategy(), new org.apache.http.conn.ssl.AllowAllHostnameVerifier());
@Nullable
final String tlsVersion = destination.getPropertiesByName().get("TLSVersion");
final SSLContext sslContext = SSLContext.getInstance(tlsVersion != null ? tlsVersion : "TLSv1.2");
final TrustManager trustAllTrustManager = new TrustAllManager();
sslContext.init(null, new TrustManager[] { trustAllTrustManager }, null);
}
}
private static class TrustAllManager implements X509TrustManager
{
@Override
public void checkClientTrusted( final X509Certificate[] x509Certificates, final String s )
{
// do nothing on purpose
}
@Override
public void checkServerTrusted( final X509Certificate[] x509Certificates, final String s )
{
// do nothing on purpose
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[0];
}
}
private void setProxyOfSoapCall( @Nonnull final ServiceT service, @Nonnull final Destination destination )
{
final Optional proxyConfiguration = destination.getProxyConfiguration();
if( proxyConfiguration.isPresent() ) {
final String proxyHost = proxyConfiguration.get().getUri().getHost();
final int proxyPort = proxyConfiguration.get().getUri().getPort();
final HttpTransportProperties.ProxyProperties proxyProperties =
new HttpTransportProperties.ProxyProperties();
proxyProperties.setProxyName(proxyHost);
proxyProperties.setProxyPort(proxyPort);
service._getServiceClient().getOptions().setProperty(HTTPConstants.PROXY, proxyProperties);
if( logger.isDebugEnabled() ) {
logger.debug("Setting proxy for SOAP call: " + proxyHost + ":" + proxyPort + ".");
}
}
}
private
void
setTargetUriOfSoapCall( @Nonnull final ServiceT service, @Nonnull final ErpConfigContext configContext )
throws SoapException
{
final Destination destination = DestinationAccessor.getDestination(configContext.getDestinationName());
final URI originalSoapUri = getTargetUriOfSoapCall(service);
if( logger.isDebugEnabled() ) {
logger.debug("Original SOAP service URI from WSDL file: " + originalSoapUri + ".");
}
final List queryStringParams = Lists.newArrayList();
if( !configContext.getSapClient().isEmpty() ) {
queryStringParams.add(
new BasicNameValuePair(
ErpConfigContext.DEFAULT_SAP_CLIENT_PROPERTY,
configContext.getSapClient().getValue()));
}
queryStringParams.add(
new BasicNameValuePair(ErpConfigContext.DEFAULT_LOCALE_PROPERTY, configContext.getLocale().toString()));
final URI destinationUri = destination.getUri();
final URI targetUri;
try {
targetUri =
new URIBuilder()
.setScheme(destinationUri.getScheme())
.setUserInfo(destinationUri.getUserInfo())
.setHost(destinationUri.getHost())
.setPort(destinationUri.getPort())
.setPath(originalSoapUri.getPath())
.setParameters(queryStringParams)
.build();
}
catch( final URISyntaxException e ) {
throw new SoapException("Error while constructing target URI of SOAP service.", e);
}
if( logger.isDebugEnabled() ) {
logger.debug("Determined target URI of SOAP service: " + targetUri + ".");
}
setTargetUriOfSoapCall(service, targetUri);
}
private void setHeadersOfSoapCall( @Nonnull final ServiceT service, @Nonnull final Destination destination )
{
final Map soapHeaders = Maps.newHashMap();
final List destinationHeaders = destination.getHeaders(destination.getUri());
for( final Header header : destinationHeaders ) {
soapHeaders.put(header.getName(), header.getValue());
}
service._getServiceClient().getOptions().setProperty(HTTPConstants.HTTP_HEADERS, soapHeaders);
}
private URI getTargetUriOfSoapCall( @Nonnull final ServiceT service )
throws SoapException
{
final String address = service._getServiceClient().getOptions().getTo().getAddress();
if( address.isEmpty() ) {
throw new SoapException(
"URI pointing to SOAP service is empty. Ensure that XML attribute location of the XML tag soap:address inside the respective WSDL file contains as valid URI.");
}
final URI targetUri;
try {
targetUri = new URI(address);
}
catch( final URISyntaxException e ) {
throw new SoapException(
"Error while reading target URI of SOAP service coming from WSDL: " + address + " is not a valid URI.",
e);
}
return targetUri;
}
private void setTargetUriOfSoapCall( @Nonnull final ServiceT service, @Nonnull final URI targetUri )
{
service._getServiceClient().getOptions().getTo().setAddress(targetUri.toString());
}
/**
* Executes a query against a SOAP service based on the given function.
*
* @param function
* The function that calls the SOAP service and returns the relevant result.
* @param
* The result type of the given function.
*
* @return The result of the function.
*
* @throws SoapException
* If there is an issue while executing the query.
*/
@Nonnull
public ReturnT execute( @Nonnull final CheckedFunction1 function )
throws SoapException
{
try {
return function.apply(service);
}
catch( final SoapException t ) {
throw t;
}
// CHECKSTYLE:OFF
catch( final Throwable t ) {
throw new SoapException(t);
}
// CHECKSTYLE:ON
}
}