All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.sdk.s4hana.connectivity.soap.SoapQuery Maven / Gradle / Ivy

There is a newer version: 2.28.0
Show newest version
/*
 * 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 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 } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy