/* * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wso2.carbon.event.output.adapter.soap; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.util.AXIOMUtil; import org.apache.axis2.AxisFault; import org.apache.axis2.addressing.EndpointReference; import org.apache.axis2.addressing.RelatesTo; import org.apache.axis2.client.Options; import org.apache.axis2.client.ServiceClient; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; import org.apache.axis2.description.TransportInDescription; import org.apache.axis2.description.TransportOutDescription; import org.apache.axis2.transport.http.HTTPConstants; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.neethi.Policy; import org.apache.neethi.PolicyEngine; import org.apache.rampart.RampartMessageData; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.event.output.adapter.core.EventAdapterUtil; import org.wso2.carbon.event.output.adapter.core.OutputEventAdapter; import org.wso2.carbon.event.output.adapter.core.OutputEventAdapterConfiguration; import org.wso2.carbon.event.output.adapter.core.exception.OutputEventAdapterException; import org.wso2.carbon.event.output.adapter.core.exception.OutputEventAdapterRuntimeException; import org.wso2.carbon.event.output.adapter.core.exception.TestConnectionNotSupportedException; import org.wso2.carbon.event.output.adapter.soap.internal.util.SoapEventAdapterConstants; import org.wso2.carbon.utils.ServerConstants; import javax.xml.stream.XMLStreamException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.*; public class SoapEventAdapter implements OutputEventAdapter { private static final Log log = LogFactory.getLog(SoapEventAdapter.class); private OutputEventAdapterConfiguration eventAdapterConfiguration; private Map<String, String> globalProperties; private ExecutorService executorService; private ConfigurationContext configContext; private int tenantId; public SoapEventAdapter(OutputEventAdapterConfiguration eventAdapterConfiguration, Map<String, String> globalProperties) { this.eventAdapterConfiguration = eventAdapterConfiguration; this.globalProperties = globalProperties; } @Override public void init() throws OutputEventAdapterException { tenantId= PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); //executorService will be assigned if it is null if (executorService == null) { int minThread; int maxThread; long defaultKeepAliveTime; int jobQueueSize; //If global properties are available those will be assigned else constant values will be assigned if (globalProperties.get(SoapEventAdapterConstants.ADAPTER_MIN_THREAD_POOL_SIZE_NAME) != null) { minThread = Integer.parseInt(globalProperties.get( SoapEventAdapterConstants.ADAPTER_MIN_THREAD_POOL_SIZE_NAME)); } else { minThread = SoapEventAdapterConstants.ADAPTER_MIN_THREAD_POOL_SIZE; } if (globalProperties.get(SoapEventAdapterConstants.ADAPTER_MAX_THREAD_POOL_SIZE_NAME) != null) { maxThread = Integer.parseInt(globalProperties.get( SoapEventAdapterConstants.ADAPTER_MAX_THREAD_POOL_SIZE_NAME)); } else { maxThread = SoapEventAdapterConstants.ADAPTER_MAX_THREAD_POOL_SIZE; } if (globalProperties.get(SoapEventAdapterConstants.ADAPTER_KEEP_ALIVE_TIME_NAME) != null) { defaultKeepAliveTime = Integer.parseInt(globalProperties.get( SoapEventAdapterConstants.ADAPTER_KEEP_ALIVE_TIME_NAME)); } else { defaultKeepAliveTime = SoapEventAdapterConstants.DEFAULT_KEEP_ALIVE_TIME_IN_MILLIS; } if (globalProperties.get(SoapEventAdapterConstants.ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME) != null) { jobQueueSize = Integer.parseInt(globalProperties.get( SoapEventAdapterConstants.ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME)); } else { jobQueueSize = SoapEventAdapterConstants.ADAPTER_EXECUTOR_JOB_QUEUE_SIZE; } executorService = new ThreadPoolExecutor(minThread, maxThread, defaultKeepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(jobQueueSize)); } } @Override public void testConnect() throws TestConnectionNotSupportedException { throw new TestConnectionNotSupportedException("Test connection is not available"); } @Override public void connect() { try { configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem( System.getProperty(ServerConstants.CARBON_HOME) + SoapEventAdapterConstants.SERVER_CLIENT_DEPLOYMENT_DIR, System.getProperty(ServerConstants.CARBON_CONFIG_DIR_PATH) + SoapEventAdapterConstants.AXIS2_CLIENT_CONF_FILE); int axis2ClientTimeOutInMillis = SoapEventAdapterConstants.DEFAULT_AXIS2_CLIENT_CONNECTION_TIMEOUT; boolean isReuseHTTPClient = SoapEventAdapterConstants.IS_DEFAULT_AXIS2_REUSE_HTTP_CLIENT; boolean isAutoReleaseConnection = SoapEventAdapterConstants.IS_DEFAULT_AXIS2_AUTO_RELEASE_CONNECTION; int maxConnectionPerHostValue = SoapEventAdapterConstants.DEFAULT_AXIS2_MAX_CONNECTION_PER_HOST; String axi2ClientTimeOut = globalProperties.get(SoapEventAdapterConstants.AXIS2_CLIENT_CONNECTION_TIMEOUT); try { if (axi2ClientTimeOut != null) { axis2ClientTimeOutInMillis = Integer.parseInt(axi2ClientTimeOut); } } catch (NumberFormatException e) { log.error("Invalid axis2 client timeout value " + axi2ClientTimeOut + " ignoring the configuration and using default value " + axis2ClientTimeOutInMillis); } String reuseHTTPClient = globalProperties.get(SoapEventAdapterConstants.AXIS2_REUSE_HTTP_CLIENT); try { if (reuseHTTPClient != null) { isReuseHTTPClient = Boolean.parseBoolean(reuseHTTPClient); } } catch (NumberFormatException e) { log.error("Invalid Reuse HTTP Client value " + reuseHTTPClient + " ignoring the configuration and using default value " + isReuseHTTPClient); } String autoReleaseConnection = globalProperties.get(SoapEventAdapterConstants.AXIS2_AUTO_RELEASE_CONNECTION); try { if (autoReleaseConnection != null) { isAutoReleaseConnection = Boolean.parseBoolean(autoReleaseConnection); } } catch (NumberFormatException e) { log.error("Invalid Auto release connection value " + autoReleaseConnection + " ignoring the configuration and using default value " + isAutoReleaseConnection); } String maxConnectionPerHost = globalProperties.get(SoapEventAdapterConstants.AXIS2_MAX_CONNECTION_PER_HOST); try { if (maxConnectionPerHost != null) { maxConnectionPerHostValue = Integer.parseInt(maxConnectionPerHost); } } catch (NumberFormatException e) { log.error("Invalid Max connection per host value " + maxConnectionPerHost + " ignoring the configuration and using default value " + maxConnectionPerHostValue); } configContext.setProperty(HTTPConstants.REUSE_HTTP_CLIENT, isReuseHTTPClient); configContext.setProperty(HTTPConstants.CACHED_HTTP_CLIENT, createMultiThreadedHttpConnectionManager(axis2ClientTimeOutInMillis, maxConnectionPerHostValue)); configContext.setProperty(HTTPConstants.AUTO_RELEASE_CONNECTION, isAutoReleaseConnection); } catch (AxisFault axisFault) { throw new OutputEventAdapterRuntimeException("Error while creating configuration context from filesystem ", axisFault); } } @Override public void publish(Object message, Map<String, String> dynamicProperties) { String url = dynamicProperties.get(SoapEventAdapterConstants.ADAPTER_CONF_SOAP_URL); String userName = dynamicProperties.get(SoapEventAdapterConstants.ADAPTER_CONF_SOAP_USERNAME); String password = dynamicProperties.get(SoapEventAdapterConstants.ADAPTER_CONF_SOAP_PASSWORD); Map<String, String> soapHeaders = this.extractHeaders(dynamicProperties.get( SoapEventAdapterConstants.ADAPTER_CONF_SOAP_HEADERS)); Map<String, String> httpHeaders = this.extractHeaders(dynamicProperties.get( SoapEventAdapterConstants.ADAPTER_CONF_HTTP_HEADERS)); try { this.executorService.submit(new SoapSender(url, message, userName, password, soapHeaders, httpHeaders)); } catch (RejectedExecutionException e) { EventAdapterUtil.logAndDrop(eventAdapterConfiguration.getName(), message, "Job queue is full", e, log, tenantId); } } @Override public void disconnect() { //not required } @Override public void destroy() { //not required } @Override public boolean isPolled() { return false; } private Map<String, String> extractHeaders(String headers) { if (headers == null || headers.trim().length() == 0) { return null; } String[] entries = headers.split(SoapEventAdapterConstants.HEADER_SEPARATOR); String[] keyValue; Map<String, String> result = new HashMap<String, String>(); for (String header : entries) { try { keyValue = header.split(SoapEventAdapterConstants.ENTRY_SEPARATOR, 2); result.put(keyValue[0].trim(), keyValue[1].trim()); } catch (Throwable e) { log.error("Header property \"" + header + "\" is not defined in the correct format.", e); } } return result; } public class SoapSender implements Runnable { private String url; private Object payload; private String username; private String password; private Map<String, String> soapHeaders; private Map<String, String> httpHeaders; public SoapSender(String url, Object payload, String username, String password, Map<String, String> soapHeaders, Map<String, String> httpHeaders) { this.url = url; this.payload = payload; this.username = username; this.password = password; this.soapHeaders = soapHeaders; this.httpHeaders = httpHeaders; } @Override public void run() { ServiceClient serviceClient = null; try { serviceClient = new ServiceClient(configContext, null); Options options = new Options(); options.setTo(new EndpointReference(url)); if (soapHeaders != null) { serviceClient.engageModule("addressing"); setSoapHeaders(soapHeaders, options); } if (httpHeaders != null) { setHttpHeaders(httpHeaders, options); } if (username != null || password != null) { options.setUserName(username); options.setPassword(password); serviceClient.engageModule("rampart"); options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, loadPolicy()); } serviceClient.setOptions(options); serviceClient.fireAndForget(AXIOMUtil.stringToOM(payload.toString())); } catch (AxisFault e) { EventAdapterUtil.logAndDrop(eventAdapterConfiguration.getName(), payload, "Cannot send to endpoint '" + url + "'", e, log, tenantId); } catch (XMLStreamException e) { EventAdapterUtil.logAndDrop(eventAdapterConfiguration.getName(), payload, "Cannot convert event to XML", e, log, tenantId); } catch (Exception e) { EventAdapterUtil.logAndDrop(eventAdapterConfiguration.getName(), payload, null, e, log, tenantId); } finally { if (serviceClient != null) { try { serviceClient.cleanup(); } catch (AxisFault axisFault) { log.error("Error while cleaning-up service client resources of Output SOAP Event Adapter '" + eventAdapterConfiguration.getName() + "' : " + axisFault.getMessage(), axisFault); } } } } private Policy loadPolicy() throws XMLStreamException { OMElement omElement = AXIOMUtil.stringToOM( "<wsp:Policy xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\"\n" + " xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"\n" + " wsu:Id=\"UTOverTransport\">\n" + " <wsp:ExactlyOne>\n" + " <wsp:All>\n" + " <sp:TransportBinding xmlns:sp=\"http://schemas.xmlsoap.org/ws/2005/07/securitypolicy\">\n" + " <wsp:Policy>\n" + " <sp:TransportToken>\n" + " <wsp:Policy>\n" + " <sp:HttpsToken RequireClientCertificate=\"false\"></sp:HttpsToken>\n" + " </wsp:Policy>\n" + " </sp:TransportToken>\n" + " <sp:AlgorithmSuite>\n" + " <wsp:Policy>\n" + " <sp:Basic256></sp:Basic256>\n" + " </wsp:Policy>\n" + " </sp:AlgorithmSuite>\n" + " <sp:Layout>\n" + " <wsp:Policy>\n" + " <sp:Lax></sp:Lax>\n" + " </wsp:Policy>\n" + " </sp:Layout>\n" + " <sp:IncludeTimestamp></sp:IncludeTimestamp>\n" + " </wsp:Policy>\n" + " </sp:TransportBinding>\n" + " <sp:SignedSupportingTokens\n" + " xmlns:sp=\"http://schemas.xmlsoap.org/ws/2005/07/securitypolicy\">\n" + " <wsp:Policy>\n" + " <sp:UsernameToken\n" + " sp:IncludeToken=\"http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient\"></sp:UsernameToken>\n" + " </wsp:Policy>\n" + " </sp:SignedSupportingTokens>\n" + " </wsp:All>\n" + " </wsp:ExactlyOne>\n" + "</wsp:Policy>"); return PolicyEngine.getPolicy(omElement); } private void setSoapHeaders(Map<String, String> headers, Options options) { for (Map.Entry<String, String> headerValue : headers.entrySet()) { try { if (headerValue.getKey().equalsIgnoreCase("SOAPAction")) { options.setAction(headerValue.getValue()); } else if (headerValue.getKey().equalsIgnoreCase("From")) { options.setFrom(new EndpointReference(headerValue.getValue())); } else if (headerValue.getKey().equalsIgnoreCase("FaultTo")) { options.setFaultTo(new EndpointReference(headerValue.getValue())); } else if (headerValue.getKey().equalsIgnoreCase("TransportIn")) { options.setTransportIn(new TransportInDescription(headerValue.getValue())); } else if (headerValue.getKey().equalsIgnoreCase("TransportInProtocol")) { options.setTransportInProtocol(headerValue.getValue()); } else if (headerValue.getKey().equalsIgnoreCase("MessageID")) { options.setMessageId(headerValue.getValue()); } else if (headerValue.getKey().equalsIgnoreCase("RelatesTo")) { options.addRelatesTo(new RelatesTo(headerValue.getValue())); } else if (headerValue.getKey().equalsIgnoreCase("ReplyTo")) { options.setReplyTo(new EndpointReference(headerValue.getValue())); } else if (headerValue.getKey().equalsIgnoreCase("TransportOut")) { options.setTransportOut(new TransportOutDescription(headerValue.getValue())); } else if (headerValue.getKey().equalsIgnoreCase("SoapVersionURI")) { options.setSoapVersionURI(headerValue.getValue()); } else if (headerValue.getKey().equalsIgnoreCase("To")) { options.setTo(new EndpointReference(headerValue.getValue())); } else if (headerValue.getKey().equalsIgnoreCase("ManageSession")) { options.setManageSession(Boolean.parseBoolean(headerValue.getValue())); } else { try { int headerParameterValue = Integer.parseInt(headerValue.getValue()); options.setProperty(headerValue.getKey(), headerParameterValue); } catch (NumberFormatException e) { options.setProperty(headerValue.getKey(), headerValue.getValue()); } } } catch (Throwable e) { //Catching the exception because there can be several exception thrown from axis2 level and we cannot drop the message because of a header issue log.warn("Invalid soap header : \"" + headerValue + "\", ignoring corresponding header..." + e.getMessage()); } } } private void setHttpHeaders(Map<String, String> headers, Options options) { List<Header> list = new ArrayList<>(); for (Map.Entry<String, String> headerValue : headers.entrySet()) { try { Header header = new Header(); header.setName(headerValue.getKey()); header.setValue(headerValue.getValue()); list.add(header); } catch (Throwable e) { //Catching the exception because there can be several exception thrown from axis2 level and we cannot drop the message because of a header issue log.warn("Invalid HTTP header : \"" + headerValue + "\", ignoring corresponding header..." + e.getMessage()); } } options.setProperty(org.apache.axis2.transport.http.HTTPConstants.HTTP_HEADERS, list); } } private HttpClient createMultiThreadedHttpConnectionManager(int connectionTimeOut, int maxConnectionPerHost) { HttpConnectionManagerParams params = new HttpConnectionManagerParams(); params.setDefaultMaxConnectionsPerHost(maxConnectionPerHost); params.setConnectionTimeout(connectionTimeOut); MultiThreadedHttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager(); httpConnectionManager.setParams(params); return new HttpClient(httpConnectionManager); } }