/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.synapse.mediators.builtin;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.util.ElementHelper;
import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.SynapseException;
import org.apache.synapse.SynapseLog;
import org.apache.synapse.aspects.AspectConfiguration;
import org.apache.synapse.aspects.ComponentType;
import org.apache.synapse.aspects.flow.statistics.StatisticIdentityGenerator;
import org.apache.synapse.aspects.flow.statistics.data.artifact.ArtifactHolder;
import org.apache.synapse.commons.json.JsonUtil;
import org.apache.synapse.commons.transaction.TranscationManger;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.endpoints.AbstractEndpoint;
import org.apache.synapse.endpoints.AddressEndpoint;
import org.apache.synapse.endpoints.DefaultEndpoint;
import org.apache.synapse.endpoints.Endpoint;
import org.apache.synapse.endpoints.EndpointDefinition;
import org.apache.synapse.mediators.AbstractMediator;
import org.apache.synapse.message.senders.blocking.BlockingMsgSender;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import org.apache.synapse.util.MessageHelper;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <callout serviceURL="string" | endpointKey="string" [action="string"] [initAxis2ClientOptions="boolean"]>
* <configuration [axis2xml="string"] [repository="string"]/>?
* <endpoint/>?
* <source xpath="expression" | key="string" | type="envelope">? <!-- key can be a MC property or entry key -->
* <target xpath="expression" | key="string"/>?
* <enableSec policy="string" | outboundPolicy="String" | inboundPolicy="String"/>?
* </callout>
*/
public class CalloutMediator extends AbstractMediator implements ManagedLifecycle {
private ConfigurationContext configCtx = null;
private String serviceURL = null;
private String action = null;
private String requestKey = null;
private SynapseXPath requestXPath = null;
private SynapseXPath targetXPath = null;
private String targetKey = null;
private String clientRepository = null;
private String axis2xml = null;
private String useServerConfig = null;
private boolean initClientOptions = true;
private Endpoint endpoint;
private String endpointKey = null;
private boolean useEnvelopeAsSource = false;
private boolean securityOn = false; //Should messages be sent using WS-Security?
private String wsSecPolicyKey = null;
private String inboundWsSecPolicyKey = null;
private String outboundWsSecPolicyKey = null;
public final static String DEFAULT_CLIENT_REPO = "./repository/deployment/client";
public final static String DEFAULT_AXIS2_XML = "./repository/conf/axis2/axis2_blocking_client.xml";
private boolean isWrappingEndpointCreated = false;
private Context txContext;
private static final String USER_TX_LOOKUP_STR = "java:comp/UserTransaction";
private static final String DISTRIBUTED_TX_BEGIN_CHECK_STR = "transport.jms.TransactionCommand=begin";
BlockingMsgSender blockingMsgSender = null;
public boolean mediate(MessageContext synCtx) {
if (synCtx.getEnvironment().isDebuggerEnabled()) {
if (super.divertMediationRoute(synCtx)) {
return true;
}
}
SynapseLog synLog = getLog(synCtx);
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("Start : Callout mediator");
if (synLog.isTraceTraceEnabled()) {
synLog.traceTrace("Message : " + synCtx.getEnvelope());
}
}
try {
if (!initClientOptions) {
blockingMsgSender.setInitClientOptions(false);
}
if (endpointKey != null) {
endpoint = synCtx.getEndpoint(endpointKey);
}
if (synLog.isTraceOrDebugEnabled()) {
if (!isWrappingEndpointCreated) {
synLog.traceOrDebug("Using the defined endpoint : " + endpoint.getName());
} else {
if (serviceURL != null) {
synLog.traceOrDebug("Using the serviceURL : " + serviceURL);
} else {
synLog.traceOrDebug("Using the To header as the EPR ");
}
if (securityOn) {
synLog.traceOrDebug("Security enabled within the Callout Mediator config");
if (wsSecPolicyKey != null) {
synLog.traceOrDebug("Using security policy key : " + wsSecPolicyKey);
} else {
if (inboundWsSecPolicyKey != null) {
synLog.traceOrDebug("Using inbound security policy key : " + inboundWsSecPolicyKey);
}
if (outboundWsSecPolicyKey != null) {
synLog.traceOrDebug("Using outbound security policy key : " + outboundWsSecPolicyKey);
}
}
}
}
}
if (isWrappingEndpointCreated) {
org.apache.axis2.context.MessageContext axis2MsgCtx = ((Axis2MessageContext) synCtx).getAxis2MessageContext();
if (Constants.VALUE_TRUE.equals(axis2MsgCtx.getProperty(Constants.Configuration.ENABLE_MTOM))) {
((AbstractEndpoint) endpoint).getDefinition().setUseMTOM(true);
}
}
if (this.serviceURL != null && this.serviceURL.contains(DISTRIBUTED_TX_BEGIN_CHECK_STR)) {
try {
initContext(synCtx);
try {
TranscationManger.lookUp(txContext);
} catch (Exception e) {
handleException("Cloud not get the context name " + USER_TX_LOOKUP_STR, e, synCtx);
}
TranscationManger.beginTransaction();
org.apache.axis2.context.MessageContext axis2MsgCtx =
((Axis2MessageContext)synCtx).getAxis2MessageContext();
axis2MsgCtx.setProperty(NhttpConstants.DISTRIBUTED_TRANSACTION, TranscationManger.getTransaction());
axis2MsgCtx.setProperty(NhttpConstants.DISTRIBUTED_TRANSACTION_MANAGER,TranscationManger.getTransactionManager());
} catch (Exception e) {
handleException("Error starting transaction",synCtx);
}
}
MessageContext synapseOutMsgCtx = MessageHelper.cloneMessageContext(synCtx);
// Send the SOAP Header Blocks to support WS-Addressing
setSoapHeaderBlock(synapseOutMsgCtx);
if (!useEnvelopeAsSource
// if the payload is JSON, we do not consider the request (ie. source) path. Instead, we use the complete payload.
&& !JsonUtil.hasAJsonPayload(((Axis2MessageContext) synapseOutMsgCtx).getAxis2MessageContext())) {
SOAPBody soapBody = synapseOutMsgCtx.getEnvelope().getBody();
for (Iterator itr = soapBody.getChildElements(); itr.hasNext(); ) {
OMElement child = (OMElement) itr.next();
child.detach();
}
soapBody.addChild(getRequestPayload(synCtx));
}
if (action != null) {
synapseOutMsgCtx.setWSAAction(action);
}
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("About to invoke the service");
if (synLog.isTraceTraceEnabled()) {
synLog.traceTrace("Request message payload : " + synapseOutMsgCtx.getEnvelope());
}
}
MessageContext resultMsgCtx = null;
try {
if ("true".equals(synCtx.getProperty(SynapseConstants.OUT_ONLY))) {
blockingMsgSender.send(endpoint, synapseOutMsgCtx);
} else {
resultMsgCtx = blockingMsgSender.send(endpoint, synapseOutMsgCtx);
setResponseHttpSc(resultMsgCtx, synCtx);
if ("true".equals(resultMsgCtx.getProperty(SynapseConstants.BLOCKING_SENDER_ERROR))) {
handleFault(synCtx, (Exception) resultMsgCtx.getProperty(SynapseConstants.ERROR_EXCEPTION));
}
}
} catch (Exception ex) {
handleFault(synCtx, ex);
}
if (synLog.isTraceTraceEnabled()) {
synLog.traceTrace("Response payload received : " + resultMsgCtx.getEnvelope());
}
if (resultMsgCtx != null && resultMsgCtx.getEnvelope() != null) {
org.apache.axis2.context.MessageContext resultAxisMsgCtx =
((Axis2MessageContext) resultMsgCtx).getAxis2MessageContext();
org.apache.axis2.context.MessageContext inAxisMsgCtx =
((Axis2MessageContext) synCtx).getAxis2MessageContext();
if (JsonUtil.hasAJsonPayload(resultAxisMsgCtx)) {
JsonUtil.cloneJsonPayload(resultAxisMsgCtx, inAxisMsgCtx);
} else {
if (targetXPath != null) {
Object o = targetXPath.evaluate(synCtx);
OMElement result = resultMsgCtx.getEnvelope().getBody().getFirstElement();
if (o != null && o instanceof OMElement) {
OMNode tgtNode = (OMElement) o;
tgtNode.insertSiblingAfter(result);
tgtNode.detach();
} else if (o != null && o instanceof List && !((List) o).isEmpty()) {
// Always fetches *only* the first
OMNode tgtNode = (OMElement) ((List) o).get(0);
tgtNode.insertSiblingAfter(result);
tgtNode.detach();
} else {
handleException("Evaluation of target XPath expression : " +
targetXPath.toString() + " did not yeild an OMNode", synCtx);
}
} else if (targetKey != null) {
OMElement result = resultMsgCtx.getEnvelope().getBody().getFirstElement();
synCtx.setProperty(targetKey, result);
} else {
synCtx.setEnvelope(resultMsgCtx.getEnvelope());
}
}
// Set HTTP Status code
inAxisMsgCtx.setProperty(SynapseConstants.HTTP_SC,
resultAxisMsgCtx.getProperty(SynapseConstants.HTTP_SC));
if ("false".equals(synCtx.getProperty(
SynapseConstants.BLOCKING_SENDER_PRESERVE_REQ_HEADERS))) {
inAxisMsgCtx.setProperty(
org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS,
resultAxisMsgCtx.getProperty(
org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS));
}
} else {
synLog.traceOrDebug("Service returned a null response");
}
} catch (AxisFault e) {
handleException("Error invoking service : " + serviceURL +
(action != null ? " with action : " + action : ""), e, synCtx);
} catch (JaxenException e) {
handleException("Error while evaluating the XPath expression: " + targetXPath,
e, synCtx);
}
synLog.traceOrDebug("End : Callout mediator");
return true;
}
private void setSoapHeaderBlock(MessageContext synCtx) {
// Send the SOAP Header Blocks to support WS-Addressing
if (synCtx.getEnvelope().getHeader() != null) {
Iterator iHeader = synCtx.getEnvelope().getHeader().getChildren();
SOAPFactory fac;
if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(synCtx.getEnvelope().getBody()
.getNamespace().getNamespaceURI())) {
fac = OMAbstractFactory.getSOAP11Factory();
} else {
fac = OMAbstractFactory.getSOAP12Factory();
}
List<OMNode> newHeaderNodes = new ArrayList<OMNode>();
while (iHeader.hasNext()) {
try {
Object element = iHeader.next();
if (element instanceof OMElement) {
newHeaderNodes.add(ElementHelper.toSOAPHeaderBlock((OMElement) element, fac).cloneOMElement());
}
iHeader.remove();
} catch (OMException e) {
log.error("Unable to convert to SoapHeader Block", e);
} catch (Exception e) {
log.error("Unable to convert to SoapHeader Block", e);
}
}
for (OMNode newHeaderNode : newHeaderNodes) {
synCtx.getEnvelope().getHeader().addChild(newHeaderNode);
}
}
}
private void handleFault(MessageContext synCtx, Exception ex) {
synCtx.setProperty(SynapseConstants.SENDING_FAULT, Boolean.TRUE);
if (ex instanceof AxisFault) {
AxisFault axisFault = (AxisFault) ex;
int errorCode = SynapseConstants.CALLOUT_OPERATION_FAILED;
if (axisFault.getFaultCodeElement() != null && !"".equals(axisFault.getFaultCodeElement().getText())) {
try {
errorCode = Integer.parseInt(axisFault.getFaultCodeElement().getText());
} catch (NumberFormatException e) {
errorCode = SynapseConstants.CALLOUT_OPERATION_FAILED;
}
}
synCtx.setProperty(SynapseConstants.ERROR_CODE, errorCode);
if (axisFault.getMessage() != null) {
synCtx.setProperty(SynapseConstants.ERROR_MESSAGE,
axisFault.getMessage());
} else {
synCtx.setProperty(SynapseConstants.ERROR_MESSAGE, "Error while performing " +
"the callout operation");
}
if (axisFault.getFaultDetailElement() != null) {
if (axisFault.getFaultDetailElement().getFirstElement() != null) {
synCtx.setProperty(SynapseConstants.ERROR_DETAIL,
axisFault.getFaultDetailElement().getFirstElement());
} else {
synCtx.setProperty(SynapseConstants.ERROR_DETAIL,
axisFault.getFaultDetailElement().getText());
}
}
}
synCtx.setProperty(SynapseConstants.ERROR_EXCEPTION, ex);
throw new SynapseException("Error while performing the callout operation", ex);
}
private OMElement getRequestPayload(MessageContext synCtx) throws AxisFault {
if (requestKey != null) {
Object request = synCtx.getProperty(requestKey);
if (request == null) {
request = synCtx.getEntry(requestKey);
}
if (request != null && request instanceof OMElement) {
return (OMElement) request;
} else {
handleException("The property : " + requestKey + " is not an OMElement", synCtx);
}
} else if (requestXPath != null) {
try {
Object o = requestXPath.evaluate(MessageHelper.cloneMessageContext(synCtx));
if (o instanceof OMElement) {
return (OMElement) o;
} else if (o instanceof List && !((List) o).isEmpty()) {
return (OMElement) ((List) o).get(0); // Always fetches *only* the first
} else {
handleException("The evaluation of the XPath expression : "
+ requestXPath.toString() + " did not result in an OMElement", synCtx);
}
} catch (JaxenException e) {
handleException("Error evaluating XPath expression : "
+ requestXPath.toString(), e, synCtx);
}
}
return null;
}
public void init(SynapseEnvironment synEnv) {
try {
configCtx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(
clientRepository != null ? clientRepository : DEFAULT_CLIENT_REPO,
axis2xml != null ? axis2xml : DEFAULT_AXIS2_XML);
if (serviceURL != null) {
serviceURL = changeEndPointReference(serviceURL);
}
blockingMsgSender = new BlockingMsgSender();
blockingMsgSender.setConfigurationContext(configCtx);
blockingMsgSender.init();
EndpointDefinition endpointDefinition = null;
if (serviceURL != null) {
// If Service URL is specified, it is given the highest priority
endpoint = new AddressEndpoint();
endpointDefinition = new EndpointDefinition();
endpointDefinition.setAddress(serviceURL);
((AddressEndpoint) endpoint).setDefinition(endpointDefinition);
isWrappingEndpointCreated = true;
} else if (endpoint == null && endpointKey == null) {
// Use a default endpoint in this case - i.e. the To header
endpoint = new DefaultEndpoint();
endpointDefinition = new EndpointDefinition();
((DefaultEndpoint) endpoint).setDefinition(endpointDefinition);
isWrappingEndpointCreated = true;
}
// If the endpoint is specified, we'll look it up at mediation time.
if (endpointDefinition != null && isSecurityOn()) {
endpointDefinition.setSecurityOn(true);
if (wsSecPolicyKey != null) {
endpointDefinition.setWsSecPolicyKey(wsSecPolicyKey);
} else {
if (inboundWsSecPolicyKey != null) {
endpointDefinition.setInboundWsSecPolicyKey(inboundWsSecPolicyKey);
}
if (outboundWsSecPolicyKey != null) {
endpointDefinition.setOutboundWsSecPolicyKey(outboundWsSecPolicyKey);
}
}
}
} catch (AxisFault e) {
String msg = "Error initializing callout mediator : " + e.getMessage();
log.error(msg, e);
throw new SynapseException(msg, e);
}
}
public void destroy() {
try {
configCtx.terminate();
} catch (AxisFault ignore) {}
}
public String getServiceURL() {
return serviceURL;
}
public void setServiceURL(String serviceURL) {
this.serviceURL = serviceURL;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getUseServerConfig() {
return useServerConfig;
}
public void setUseServerConfig(String useServerConfig) {
this.useServerConfig = useServerConfig;
}
public String getRequestKey() {
return requestKey;
}
public void setRequestKey(String requestKey) {
this.requestKey = requestKey;
}
public void setRequestXPath(SynapseXPath requestXPath) throws JaxenException {
this.requestXPath = requestXPath;
}
public void setTargetXPath(SynapseXPath targetXPath) throws JaxenException {
this.targetXPath = targetXPath;
}
public String getTargetKey() {
return targetKey;
}
public void setTargetKey(String targetKey) {
this.targetKey = targetKey;
}
public SynapseXPath getRequestXPath() {
return requestXPath;
}
public SynapseXPath getTargetXPath() {
return targetXPath;
}
public String getClientRepository() {
return clientRepository;
}
public void setClientRepository(String clientRepository) {
this.clientRepository = clientRepository;
}
public String getAxis2xml() {
return axis2xml;
}
public void setAxis2xml(String axis2xml) {
this.axis2xml = axis2xml;
}
public void setEndpointKey(String key) {
this.endpointKey = key;
}
public String getEndpointKey() {
return endpointKey;
}
public boolean getInitClientOptions() {
return initClientOptions;
}
public void setInitClientOptions(boolean initClientOptions) {
this.initClientOptions = initClientOptions;
}
public boolean isUseEnvelopeAsSource() {
return useEnvelopeAsSource;
}
public void setUseEnvelopeAsSource(boolean useEnvelopeAsSource) {
this.useEnvelopeAsSource = useEnvelopeAsSource;
}
/**
* Is WS-Security turned on on this endpoint?
*
* @return true if on
*/
public boolean isSecurityOn() {
return securityOn;
}
/**
* Request that WS-Sec be turned on/off on this endpoint
*
* @param securityOn a boolean flag indicating security is on or not
*/
public void setSecurityOn(boolean securityOn) {
this.securityOn = securityOn;
}
/**
* Return the Rampart Security configuration policys' 'key' to be used (See Rampart)
*
* @return the Rampart Security configuration policys' 'key' to be used (See Rampart)
*/
public String getWsSecPolicyKey() {
return wsSecPolicyKey;
}
/**
* Set the Rampart Security configuration policys' 'key' to be used (See Rampart)
*
* @param wsSecPolicyKey the Rampart Security configuration policys' 'key' to be used
*/
public void setWsSecPolicyKey(String wsSecPolicyKey) {
this.wsSecPolicyKey = wsSecPolicyKey;
}
/**
* Get the outbound security policy key. This is used when we specify different policies for
* inbound and outbound.
*
* @return outbound security policy key
*/
public String getOutboundWsSecPolicyKey() {
return outboundWsSecPolicyKey;
}
/**
* Set the outbound security policy key.This is used when we specify different policies for
* inbound and outbound.
*
* @param outboundWsSecPolicyKey outbound security policy key.
*/
public void setOutboundWsSecPolicyKey(String outboundWsSecPolicyKey) {
this.outboundWsSecPolicyKey = outboundWsSecPolicyKey;
}
/**
* Get the inbound security policy key. This is used when we specify different policies for
* inbound and outbound.
*
* @return inbound security policy key
*/
public String getInboundWsSecPolicyKey() {
return inboundWsSecPolicyKey;
}
/**
* Set the inbound security policy key. This is used when we specify different policies for
* inbound and outbound.
* @param inboundWsSecPolicyKey inbound security policy key.
*/
public void setInboundWsSecPolicyKey(String inboundWsSecPolicyKey) {
this.inboundWsSecPolicyKey = inboundWsSecPolicyKey;
}
/**
* This method checks for dynamic url in callout medaitor and replace it with given system properties.
* properties has to given as -D{parameter}={value}
* @param epr end point url
* @return fixed end point url
*/
private String changeEndPointReference(String epr) {
if (epr.toLowerCase().contains("system.prop")) {
Pattern p = Pattern.compile("\\{(.*?)\\}");
Matcher m = p.matcher(epr);
Map<String, String> result = new HashMap<String, String>();
while (m.find()) {
result.put(m.group(1), "");
String propName = System.getProperty(m.group(1).replace("system.prop.", ""));
if (propName != null) {
epr = epr.replace("{" + m.group(1) + "}", propName);
} else {
log.warn("System property is not initialized");
}
}
log.info("Dynamic properties of url are replaced");
}
return epr;
}
/**
* Get the defined endpoint
*
* @return endpoint
*/
public Endpoint getEndpoint() {
if (!isWrappingEndpointCreated) {
return endpoint;
}
return null;
}
/**
* Set the defined endpoint
*
* @param endpoint defined endpoint
*/
public void setEndpoint(Endpoint endpoint) {
this.endpoint = endpoint;
}
private void setResponseHttpSc(MessageContext sourceSynCtx, MessageContext destinationSynCtx) {
if (sourceSynCtx != null) {
org.apache.axis2.context.MessageContext axis2MessageContext =
((Axis2MessageContext) destinationSynCtx).getAxis2MessageContext();
axis2MessageContext.setProperty(SynapseConstants.HTTP_SC,
((Axis2MessageContext) sourceSynCtx).getAxis2MessageContext()
.getProperty(SynapseConstants.HTTP_SC));
}
}
@Override
public void setComponentStatisticsId(ArtifactHolder holder) {
if (getAspectConfiguration() == null) {
configure(new AspectConfiguration(getMediatorName()));
}
String cloneId = StatisticIdentityGenerator.getIdForComponent(getMediatorName(), ComponentType.MEDIATOR, holder);
getAspectConfiguration().setUniqueId(cloneId);
if (endpointKey != null) {
String childId = StatisticIdentityGenerator.getIdReferencingComponent(endpointKey, ComponentType.ENDPOINT, holder);
StatisticIdentityGenerator.reportingEndEvent(childId, ComponentType.SEQUENCE, holder);
} else if (endpoint != null) {
endpoint.setComponentStatisticsId(holder);
}
StatisticIdentityGenerator.reportingEndEvent(cloneId, ComponentType.MEDIATOR, holder);
}
private void initContext(MessageContext synCtx) {
try {
txContext = new InitialContext();
} catch (NamingException e) {
handleException("Cloud not create initial context", e, synCtx);
}
}
}