org.ow2.petals.binding.soap.listener.outgoing.ServiceClientPoolObjectFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of petals-bc-soap Show documentation
Show all versions of petals-bc-soap Show documentation
The PEtALS SOAP JBI binding component based on Axis2 and Jetty.
The newest version!
/**
* Copyright (c) 2008-2012 EBM WebSourcing, 2012-2023 Linagora
*
* This program/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 program/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 program/library; If not, see http://www.gnu.org/licenses/
* for the GNU Lesser General Public License version 2.1.
*/
package org.ow2.petals.binding.soap.listener.outgoing;
import static org.apache.axis2.addressing.AddressingConstants.DISABLE_ADDRESSING_FOR_OUT_MESSAGES;
import static org.ow2.petals.binding.soap.SoapConstants.Axis2.OUTGOING_SERVICE_CLIENT_PREFIX;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.logging.Level;
import javax.jbi.messaging.MessagingException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.stream.XMLStreamException;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.OutInAxisOperation;
import org.apache.axis2.description.OutOnlyAxisOperation;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.description.WSDL2Constants;
import org.apache.axis2.kernel.TransportSender;
import org.apache.axis2.kernel.http.HTTPConstants;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.apache.axis2.transport.http.impl.httpclient4.HTTPClient4TransportSender;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.neethi.Policy;
import org.ow2.petals.binding.soap.ServiceContext;
import org.ow2.petals.binding.soap.util.AxisServicesHelper;
import org.ow2.petals.binding.soap.util.SUPropertiesHelper;
import org.ow2.petals.component.framework.api.Message.MEPConstants;
import org.ow2.petals.component.framework.api.configuration.SuConfigurationParameters;
import org.ow2.petals.component.framework.jbidescriptor.generated.Provides;
import com.ebmwebsourcing.easycommons.uuid.QualifiedUUIDGenerator;
/**
* Get new Axis2 ServiceClient from the pool factory.
* @author Christophe DENEUX - Cap Gemini
* @author Christophe HAMERLING - EBM WebSourcing
*/
public class ServiceClientPoolObjectFactory extends BasePooledObjectFactory {
private static final String ORIGINAL_OPTIONS_TO_RESTORE_ON_RETURN = "org.ow2.petals.binding.soap.SoapComponentContext.ORIGINAL_OPTIONS_TO_RESTORE_ON_RETURN";
private final ServiceClientConfiguration serviceClientConfiguration;
private final ConfigurationContext configuration;
private final ServiceContext context;
private final QualifiedUUIDGenerator uuidGenerator = new QualifiedUUIDGenerator(OUTGOING_SERVICE_CLIENT_PREFIX);
/**
* Creates a new instance of ServiceClientPoolObjectFactory
*
* @param serviceClientConfiguration
* @param serviceContext
* @param configuration
*/
public ServiceClientPoolObjectFactory(final ServiceClientConfiguration serviceClientConfiguration,
final ServiceContext serviceContext, final ConfigurationContext configuration) {
this.serviceClientConfiguration = serviceClientConfiguration;
this.configuration = configuration;
this.context = serviceContext;
}
/**
* Create the SOAP options of the outgoing SOAP message
*
* @param address
* the address of the partner service
* @param soapAction
* the SOAP action
* @param extensions
* the SU extension
* @return the Axis 2 client options
* @throws MessagingException
* if there is an error to set transport
* @throws IOException
*/
private Options createOptions() throws MessagingException {
final Options options = new Options();
final SuConfigurationParameters extensions = this.context.getExtensions();
try {
// set destination address AND check if the address is a valid URI
// TODO shouldn't it be an URL?
options.setTo(new EndpointReference(new URI(this.serviceClientConfiguration.address).toString()));
} catch (final URISyntaxException e) {
throw new MessagingException(
"Invalid external web-service address: '" + this.serviceClientConfiguration.address + "'");
}
// set the content type (necessary for the formatter)
options.setProperty(org.apache.axis2.Constants.Configuration.MESSAGE_TYPE, HTTPConstants.MEDIA_TYPE_APPLICATION_SOAP_XML);
// disable WSA-Addressing
if (!SUPropertiesHelper.isWSAEnabled(extensions)) {
options.setProperty(DISABLE_ADDRESSING_FOR_OUT_MESSAGES, Boolean.TRUE);
}
// set the soap action
if (this.serviceClientConfiguration.soapAction != null) {
options.setAction(this.serviceClientConfiguration.soapAction);
}
// Get the soapEnvelopeNamespaceURI version to use - optional,
// default
// is 1.1 version
final String soapEnvelopeNamespaceURI = SUPropertiesHelper.retrieveSOAPEnvelopeNamespaceURI(extensions);
options.setSoapVersionURI(soapEnvelopeNamespaceURI);
// create and register transports
final ConnectionSocketFactory connectionSocketFactory = this.createConnectionSocketFactory();
final Registry socketFactoryRegistry = this.registerTransport(connectionSocketFactory,
options);
// get proxy settings if they are defined in the extensions
final HttpTransportProperties.ProxyProperties proxyProperties = SUPropertiesHelper
.retrieveProxySettings(extensions, context.getLogger());
if (proxyProperties != null) {
options.setProperty(HTTPConstants.PROXY, proxyProperties);
}
// prevent Axis from transforming SOAP fault to Axis fault
options.setExceptionToBeThrownOnSOAPFault(false);
SUPropertiesHelper.setBasicAuthentication(extensions, options);
// note: we need integer and not long there, hence the use of BigDecimal for the conversion (it will use the max
// it can to fit an int) (we could have used intValueExact but we don't really want to throw an exception
// here... we will log a warning)
final int intTimeout = new BigDecimal(this.serviceClientConfiguration.timeout).intValue();
if (this.serviceClientConfiguration.timeout != (int) this.serviceClientConfiguration.timeout) {
this.context.getLogger().log(Level.WARNING,
"Provides timeout was set to " + this.serviceClientConfiguration.timeout
+ " but Axis uses int to store value and the long value can't fit in an int, using "
+ intTimeout + ".");
}
// These two are used by CommonsHTTPTransportSender (that we use in the transport, see
// setTransport()) and are used (over the values set in the connection manager that we don't set, because
// it is useless) by AbstractHTTPSender.setTimeouts()
options.setProperty(HTTPConstants.CONNECTION_TIMEOUT, intTimeout);
options.setProperty(HTTPConstants.SO_TIMEOUT, intTimeout);
// this is used by OutInAxisOperationClient to send asynchronously
options.setTimeOutInMilliSeconds(this.serviceClientConfiguration.timeout);
// Theoretically (according to Options documentation), one should use HTTPConstants.REUSE_HTTP_CLIENT
// for it to be used, but in practice it is not! Still, we need to prevent reuse of the HttpState of the
// HttpClient: this is done per borrowed ServiceClient in SoapComponentContext.borrowServiceClient()
final HttpClient hc = HttpClientBuilder.create()
.setConnectionManager(this.createHttpClientConnectionManager(socketFactoryRegistry))
.build();
options.setProperty(HTTPConstants.CACHED_HTTP_CLIENT, hc);
return options;
}
private ConnectionSocketFactory createConnectionSocketFactory() throws MessagingException {
final SuConfigurationParameters extensions = this.context.getExtensions();
// get transport protocol
final URI uri = URI.create(this.serviceClientConfiguration.address);
final String scheme = uri.getScheme();
String transport = null;
if (scheme == null) {
transport = Constants.TRANSPORT_HTTP;
} else {
transport = scheme;
}
if (transport.equals(Constants.TRANSPORT_HTTP)) {
return null;
} else if (transport.equals(Constants.TRANSPORT_HTTPS)) {
final KeyStore keystore;
final String keystoreFileStr = SUPropertiesHelper.getKeystoreFile(extensions);
if (keystoreFileStr != null) {
final File keystoreFile = new File(keystoreFileStr);
final String keystorePassword = SUPropertiesHelper.getKeystorePassword(extensions);
try (final InputStream ksInputStream = new FileInputStream(keystoreFile)) {
keystore = KeyStore.getInstance("JKS");
keystore.load(ksInputStream, keystorePassword.toCharArray());
} catch (final IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
throw new MessagingException(String.format("Error reading the keystore '%s'", keystoreFileStr), e);
}
if (context.getLogger().isLoggable(Level.INFO)) {
context.getLogger().log(Level.INFO, "Client authentication is enabled.");
}
} else {
keystore = null;
}
final TrustManager[] trustManagers;
final String truststoreFileStr = SUPropertiesHelper.getTruststoreFile(extensions);
if (truststoreFileStr != null) {
final File truststoreFile = new File(truststoreFileStr);
final String truststorePassword = SUPropertiesHelper.getTruststorePassword(extensions);
try (final InputStream ksInputStream = new FileInputStream(truststoreFile)) {
final KeyStore truststore = KeyStore.getInstance("JKS");
truststore.load(ksInputStream, truststorePassword.toCharArray());
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystore);
trustManagers = trustManagerFactory.getTrustManagers();
} catch (final IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
throw new MessagingException(String.format("Error reading the truststore '%s'", truststoreFileStr),e);
}
} else {
trustManagers = null;
}
try {
final SSLContext sslContext = SSLContext.getDefault();
sslContext.init(null, trustManagers, new SecureRandom());
return new SSLConnectionSocketFactory(sslContext);
} catch (final NoSuchAlgorithmException | KeyManagementException e) {
throw new MessagingException("Error creating SSL context",e);
}
} else {
throw new UnsupportedOperationException(String
.format("Transport protocol '%s' (extracted from url '%s') not supported.", transport,
this.serviceClientConfiguration.address));
}
}
private Registry registerTransport(final ConnectionSocketFactory connectionSocketFactory,
final Options options)
throws MessagingException {
final SuConfigurationParameters extensions = context.getExtensions();
// get transport protocol
final URI uri = URI.create(this.serviceClientConfiguration.address);
final String scheme = uri.getScheme();
String transport = null;
if (scheme == null) {
transport = Constants.TRANSPORT_HTTP;
} else {
transport = scheme;
}
TransportSender sender;
TransportOutDescription transportOutDescription;
if (transport.equals(Constants.TRANSPORT_HTTP)) {
// For HTTP Transport, no specific connection socket factory is required
assert connectionSocketFactory == null;
// set transport protocol for ingoing message (response)
options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
// set transport out description
transportOutDescription = new TransportOutDescription("http");
// set the HTTP transport sender
if (SUPropertiesHelper.isAxis1CompatibilityEnabled(extensions)) {
sender = new Axis1SOAPFaultHTTPTransportSender();
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE,
"Set the customized Axis 2 transport sender: "
+ sender.getClass().getName());
}
} else {
sender = new HTTPClient4TransportSender();
}
} else if (transport.equals(Constants.TRANSPORT_HTTPS)) {
// For HTTPS Transport, a specific connection socket factory is required
assert connectionSocketFactory != null;
// set transport protocol for ingoing message (response)
options.setTransportInProtocol(Constants.TRANSPORT_HTTPS);
transportOutDescription = new TransportOutDescription("https");
sender = new HTTPClient4TransportSender();
/* final Registry socketFactoryRegistry = RegistryBuilder.create().register("https", sslsf).build();
httpClientConnectionManager = this.createHttpClientConnectionManager(socketFactoryRegistry);
HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).setConnectionManagerShared(true).build();
ProtocolSocketFactory socketFactory;
if (truststoreURL == null && keystoreURL == null) {
socketFactory = new EasySSLProtocolSocketFactory();
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, "Client and server authentications are disabled.");
}
} else {
socketFactory = new AuthSSLProtocolSocketFactory(keystoreURL, keystorePassword,
truststoreURL, truststorePassword);
}
int port = uri.getPort();
if (port == -1) {
port = DEFAULT_HTTPS_PORT;
}
final Protocol protocolHandler = new Protocol("https", socketFactory, port);
options.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER, protocolHandler);
*/
} else {
throw new UnsupportedOperationException(String
.format("Transport protocol '%s' (extracted from url '%s') not supported.", transport,
this.serviceClientConfiguration.address));
}
// set http PROTOCOL parameter (request)
try {
transportOutDescription.addParameter(new Parameter("PROTOCOL", "HTTP/1.1"));
} catch (final AxisFault af) {
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING,
"Can not set the http PROTOCOL parameter: " + af.getMessage());
}
}
try {
sender.init(configuration, transportOutDescription);
} catch (final AxisFault e) {
throw new MessagingException("Can not initialiaze the transport sender: "
+ e.getMessage());
}
transportOutDescription.setSender(sender);
// set the transport for outgoing message
options.setTransportOut(transportOutDescription);
// set chunked mode
options.setProperty(HTTPConstants.CHUNKED, SUPropertiesHelper.retrieveChunkedMode(extensions));
return null;
}
private HttpClientConnectionManager createHttpClientConnectionManager(final Registry socketFactoryRegistry) {
// Configure the outgoing HTTP connection pool of this web-service client
final PoolingHttpClientConnectionManager pollingHttpClientConnectionManager = socketFactoryRegistry != null ? new PoolingHttpClientConnectionManager(socketFactoryRegistry) : new PoolingHttpClientConnectionManager();
// this default to the maximum number of JBI processors
pollingHttpClientConnectionManager.setDefaultMaxPerRoute(this.serviceClientConfiguration.jbiProcessorMaxSize);
// we use the same as the max number of processor: there can't be more open connections that the number of
// processor running.
pollingHttpClientConnectionManager.setMaxTotal(this.serviceClientConfiguration.jbiProcessorMaxSize);
return pollingHttpClientConnectionManager;
}
/**
* Engage a module
*
* @param petalsServiceClient
* @param moduleName
* @throws AxisFault
*/
private void engageModule(final PetalsServiceClient petalsServiceClient,
final String moduleName) throws AxisFault {
if (this.context.getLogger().isLoggable(Level.FINE)) {
this.context.getLogger().fine("Engaging module " + moduleName);
}
petalsServiceClient.engageModule(moduleName);
}
/**
* Engage the modules for the service client. The modules are available on
* the service unit listener since they have been defined during SU
* deployment.
*
* @param petalsServiceClient
* @throws AxisFault
*/
private void engageModules(final PetalsServiceClient petalsServiceClient) throws AxisFault {
final List modules = this.context.getModules();
if (modules != null) {
for (final String name : modules) {
this.engageModule(petalsServiceClient, name);
}
}
}
@Override
public ServiceClient create() throws Exception {
if (this.context.getLogger().isLoggable(Level.FINE)) {
this.context.getLogger()
.fine("Creating a service client for : " + this.serviceClientConfiguration.address
+ ", with operation '" + this.serviceClientConfiguration.operation + "', and MEP '"
+ this.serviceClientConfiguration.mep + "'");
}
final AxisService axisService = new AxisService(this.uuidGenerator.getNewID());
final ClassLoader cl = this.context.getClassloader();
axisService.setClassLoader(cl);
final AxisOperation axisOperation;
if (MEPConstants.IN_ONLY_PATTERN.equals(this.serviceClientConfiguration.mep)) {
axisOperation = new OutOnlyAxisOperation(this.serviceClientConfiguration.operation);
} else if (MEPConstants.ROBUST_IN_ONLY_PATTERN.equals(this.serviceClientConfiguration.mep)) {
// TODO hack because of https://issues.apache.org/jira/browse/AXIS2-5718
// With RobustOutOnlyAxisOperation the fault is thrown instead of being returned as desired
axisOperation = new OutInAxisOperation(this.serviceClientConfiguration.operation);
axisOperation.setMessageExchangePattern(WSDL2Constants.MEP_URI_ROBUST_OUT_ONLY);
} else if (MEPConstants.IN_OPTIONAL_OUT_PATTERN.equals(this.serviceClientConfiguration.mep)
|| MEPConstants.IN_OUT_PATTERN.equals(this.serviceClientConfiguration.mep)) {
axisOperation = new OutInAxisOperation(this.serviceClientConfiguration.operation);
} else {
axisOperation = null;
}
axisService.addOperation(axisOperation);
// if it's null, it will never work anyway...
if (axisOperation != null) {
// not sure which one is used between this and the one set in the options...
axisOperation.setSoapAction(this.serviceClientConfiguration.soapAction);
}
// create Options for the stub
final Options options = createOptions();
// add the service parameters
try {
AxisServicesHelper.addServiceParameters(this.context.getServiceParams(), axisService);
} catch (final XMLStreamException | AxisFault e) {
this.context.getLogger().log(Level.WARNING, "Can't add service parameters", e);
}
try {
// TODO this actually does not depends on the operation, so why do we pool on that???
final PetalsServiceClient petalsServiceClient = new PetalsServiceClient(this.configuration, axisService);
petalsServiceClient.setOptions(options);
// engage the Axis2 modules
engageModules(petalsServiceClient);
// Enable the right WS-Policy
final Policy wssPolicy = SUPropertiesHelper.getWSSPolicy(this.context.getExtensions(),
context.getClassloader(), this.context.getLogger());
if (wssPolicy != null) {
axisService.getPolicySubject().attachPolicy(wssPolicy);
}
return petalsServiceClient;
} catch (final AxisFault e) {
throw new MessagingException("Can't create ServiceClient", e);
}
}
@Override
public PooledObject wrap(final ServiceClient obj) {
return new DefaultPooledObject(obj);
}
@Override
public void destroyObject(final PooledObject psc) throws Exception {
final ServiceClient sc = psc.getObject();
sc.cleanup();
sc.cleanupTransport();
}
@Override
public void activateObject(final PooledObject pooledServiceClient) throws Exception {
final ServiceClient serviceClient = pooledServiceClient.getObject();
// to avoid overriding settings that could impact other calls to this service client
// we extend the option and will restore them after
final Options originalOptions = serviceClient.getOptions();
final Options options = new Options(originalOptions);
options.setProperty(ORIGINAL_OPTIONS_TO_RESTORE_ON_RETURN, originalOptions);
}
@Override
public void passivateObject(final PooledObject pooledServiceClient) throws Exception {
final ServiceClient serviceClient = pooledServiceClient.getObject();
// we should always cleanup the transport once the response has been read (and the response must have been read
// once this service client is returned).
try {
serviceClient.cleanupTransport();
} catch (final AxisFault e) {
this.context.getLogger().log(Level.SEVERE, "Can't cleanup the transport as needed... this is not normal",
e);
}
// restore options
final Object originalOption = serviceClient.getOptions().getProperty(ORIGINAL_OPTIONS_TO_RESTORE_ON_RETURN);
if (originalOption instanceof Options) {
serviceClient.setOptions((Options) originalOption);
} else {
this.context.getLogger()
.severe("Can't find the Options I stored to restore them later... this is not normal");
}
}
}