/*
* 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.OMContainer;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.synapse.Mediator;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseLog;
import org.apache.synapse.mediators.AbstractMediator;
import org.apache.synapse.mediators.base.SequenceMediator;
import org.apache.synapse.util.MessageHelper;
import org.apache.synapse.util.xpath.SynapseXPath;
import java.util.ArrayList;
import java.util.List;
public class ForEachMediator extends AbstractMediator {
/* The xpath that will list the elements to be split */
private SynapseXPath expression = null;
private SequenceMediator sequence;
private String sequenceRef;
private String id;
private static final String FOREACH_ORIGINAL_MESSAGE = "FOREACH_ORIGINAL_MESSAGE";
private static final String FOREACH_COUNTER = "FOREACH_COUNTER";
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 : Foreach mediator");
}
if (expression == null) {
handleException("ForEach: expression is null", synCtx);
}
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("ForEach expression : " + expression.toString());
}
String idPrefix = (getId() == null) ? "" : getId() + "_";
// Set original message property
String originalMsgPropName = idPrefix + FOREACH_ORIGINAL_MESSAGE;
SOAPEnvelope originalEnvelope = MessageHelper.cloneSOAPEnvelope(synCtx.getEnvelope());
synCtx.setProperty(originalMsgPropName, originalEnvelope);
// Set counter property
String counterPropName = idPrefix + FOREACH_COUNTER;
int msgCounter = 0;
synCtx.setProperty(counterPropName, msgCounter);
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("Saved original message property : " + originalMsgPropName +
" = " + originalEnvelope +
"Initialized foreach counter property, " + counterPropName +
" = " + msgCounter);
}
SOAPEnvelope processingEnvelope = synCtx.getEnvelope();
// get the iteration elements and iterate through the list, this call
// will also detach all the iteration elements from the message and deduce the
// parent node to merge back the mediated content
DetachedElementContainer detachedElementContainer =
getDetachedMatchingElements(processingEnvelope, synCtx, expression);
List<?> splitElements = detachedElementContainer.getDetachedElements();
int splitElementCount = splitElements.size();
if (splitElementCount == 0) { // Continue the message flow if no matching elements found
return true;
}
OMContainer parent = detachedElementContainer.getParent();
if (parent == null) {
handleException("Error detecting parent element to merge", synCtx);
}
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("Splitting with XPath : " + expression + " resulted in " + splitElementCount +
" elements. Parent node for merging is : " + parent.toString());
}
// iterate through the split elements
for (Object element : splitElements) {
if (!(element instanceof OMNode)) {
handleException("Error splitting message with XPath : " + expression +
" - result not an OMNode", synCtx);
}
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("Submitting " + msgCounter + " of " + splitElementCount +
" messages for processing in sequentially, in a general loop");
}
MessageContext iteratedMsgCtx = null;
try {
iteratedMsgCtx = getIteratedMessage(synCtx, processingEnvelope,
(OMNode) element);
} catch (AxisFault axisFault) {
handleException("Error creating an iterated copy of the message", axisFault, synCtx);
}
boolean mediateResult = mediateSequence(iteratedMsgCtx);
//add the mediated element to the parent from original message context
parent.addChild(iteratedMsgCtx.getEnvelope().getBody().getFirstElement());
msgCounter++;
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("Incrementing foreach counter , " + counterPropName + " = "
+ msgCounter);
}
synCtx.setProperty(counterPropName, msgCounter);
if (!mediateResult) { // break the loop if mediate result is false
break;
}
}
//set the modified envelop to message context
try {
synCtx.setEnvelope(processingEnvelope);
} catch (AxisFault axisFault) {
handleException("Error while setting the envelope to the message context", axisFault, synCtx);
}
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("After mediation foreach counter, " + counterPropName + " = "
+ msgCounter);
synLog.traceOrDebug("End : For Each mediator");
}
return true;
}
private boolean mediateSequence(MessageContext synCtx) {
if (sequence != null) {
if (log.isDebugEnabled()) {
log.debug("Synchronously mediating using the in-line anonymous sequence");
}
return sequence.mediate(synCtx);
} else if (sequenceRef != null) {
SequenceMediator referredSequence = (SequenceMediator) synCtx.getSequence(sequenceRef);
if (referredSequence != null) {
if (!validateSequence(referredSequence)) {
handleException("ForEach: Referred sequence cannot contain Call," +
" Send or CallOut mediators", synCtx);
}
if (log.isDebugEnabled()) {
log.debug("Synchronously mediating using the sequence " + "named : " + sequenceRef);
}
return referredSequence.mediate(synCtx);
} else {
handleException("Couldn't find the sequence named : " + sequenceRef, synCtx);
}
} else {
handleException("Couldn't find sequence information", synCtx);
}
return false;
}
/**
* Validate a sequence mediator to not contain Call, CallOut or Send mediators in it.
*
* @param seqMediator Sequence Mediator to validate
* @return validity of the sequence mediator
*/
private boolean validateSequence(SequenceMediator seqMediator) {
List<Mediator> mediators = seqMediator.getList();
for (Mediator m : mediators) {
if (m instanceof CallMediator || m instanceof CalloutMediator ||
m instanceof SendMediator) {
return false;
}
}
return true;
}
/**
* Create a new message context using the given original message context,
* the envelope
* and the split result element.
*
* @param synCtx original message context
* @param omNode element which participates in the iteration replacement
* @param originalEnvelope original envelope when reaching foreach
* @return modified message context with new envelope created with omNode
* @throws AxisFault if there is a message creation failure
*/
private MessageContext getIteratedMessage(MessageContext synCtx, SOAPEnvelope originalEnvelope,
OMNode omNode) throws AxisFault {
SOAPEnvelope newEnvelope = createNewSoapEnvelope(originalEnvelope);
if (newEnvelope.getBody() != null) {
newEnvelope.getBody().addChild(omNode);
}
//set the new envelop to original message context
synCtx.setEnvelope(newEnvelope);
return synCtx;
}
private SOAPEnvelope createNewSoapEnvelope(SOAPEnvelope envelope) {
SOAPFactory fac;
if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI
.equals(envelope.getBody().getNamespace().getNamespaceURI())) {
fac = OMAbstractFactory.getSOAP11Factory();
} else {
fac = OMAbstractFactory.getSOAP12Factory();
}
return fac.getDefaultEnvelope();
}
/**
* Return the set of detached elements and parent specified by the XPath over the given envelope
*
* @param envelope SOAPEnvelope from which the elements will be extracted
* @param synCtx Message context from which to extract the elements
* @param expression SynapseXPath expression describing the elements to be extracted
* @return data container which hold the detached OMElements in the envelope matching the expression
* and the parent
*/
private DetachedElementContainer getDetachedMatchingElements(SOAPEnvelope envelope,
MessageContext synCtx,
SynapseXPath expression) {
DetachedElementContainer resultContainer = new DetachedElementContainer();
List<OMNode> elementList = new ArrayList<>();
Object o = expression.evaluate(envelope, synCtx);
if (o instanceof OMNode) {
resultContainer.setParent(((OMNode) o).getParent());
elementList.add(((OMNode) o).detach());
} else if (o instanceof List) {
List oList = (List) o;
if (oList.size() > 0) {
resultContainer.setParent((((OMNode) oList.get(0)).getParent()));
}
for (Object elem : oList) {
if (elem instanceof OMNode) {
elementList.add(((OMNode) elem).detach());
}
}
}
resultContainer.setDetachedElements(elementList);
return resultContainer;
}
/**
* Result container for detached elements and parent
*/
private class DetachedElementContainer {
private OMContainer parent;
private List<OMNode> detachedElements;
public OMContainer getParent() {
return parent;
}
public void setParent(OMContainer parent) {
this.parent = parent;
}
public List<OMNode> getDetachedElements() {
return detachedElements;
}
public void setDetachedElements(List<OMNode> detachedElements) {
this.detachedElements = detachedElements;
}
}
public SynapseXPath getExpression() {
return expression;
}
public void setExpression(SynapseXPath expression) {
this.expression = expression;
}
public SequenceMediator getSequence() {
return sequence;
}
public void setSequence(SequenceMediator sequence) {
this.sequence = sequence;
}
public String getSequenceRef() {
return sequenceRef;
}
public void setSequenceRef(String sequenceKey) {
this.sequenceRef = sequenceKey;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}